diff --git a/app/Http/Controllers/MarketController.php b/app/Http/Controllers/MarketController.php index 9a4ea329..7ac87330 100644 --- a/app/Http/Controllers/MarketController.php +++ b/app/Http/Controllers/MarketController.php @@ -8,16 +8,16 @@ use App\Services\Unzip; use Composer\CaBundle\CaBundle; use Composer\Semver\Comparator; use Exception; -use GuzzleHttp\Client; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Http; class MarketController extends Controller { - public function marketData(PluginManager $manager, Client $client) + public function marketData(PluginManager $manager) { - $plugins = $this->fetch($client)->map(function ($item) use ($manager) { + $plugins = $this->fetch()->map(function ($item) use ($manager) { $plugin = $manager->get($item['name']); if ($plugin) { @@ -40,14 +40,10 @@ class MarketController extends Controller return $plugins; } - public function download( - Request $request, - PluginManager $manager, - Client $client, - Unzip $unzip - ) { + public function download(Request $request, PluginManager $manager, Unzip $unzip) + { $name = $request->input('name'); - $plugins = $this->fetch($client); + $plugins = $this->fetch(); $metadata = $plugins->firstWhere('name', $name); if (!$metadata) { @@ -64,34 +60,33 @@ class MarketController extends Controller } $path = tempnam(sys_get_temp_dir(), $name); - try { - $client->get($metadata['dist']['url'], [ - 'sink' => $path, - 'verify' => CaBundle::getSystemCaRootBundlePath(), - ]); + $response = Http::withOptions([ + 'sink' => $path, + 'verify' => CaBundle::getSystemCaRootBundlePath(), + ])->get($metadata['dist']['url']); + + if ($response->ok()) { $unzip->extract($path, $manager->getPluginsDirs()->first()); - } catch (Exception $e) { - report($e); - return json(trans('admin.download.errors.download', ['error' => $e->getMessage()]), 1); + return json(trans('admin.plugins.market.install-success'), 0); + } else { + return json(trans('admin.download.errors.download', ['error' => $response->status()]), 1); } - - return json(trans('admin.plugins.market.install-success'), 0); } - protected function fetch(Client $client): Collection + protected function fetch(): Collection { $plugins = collect(explode(',', config('plugins.registry'))) - ->map(function ($registry) use ($client) { - try { - $body = $client->get(trim($registry), [ - 'verify' => CaBundle::getSystemCaRootBundlePath(), - ])->getBody(); - } catch (Exception $e) { - throw new Exception(trans('admin.plugins.market.connection-error', ['error' => $e->getMessage()])); - } + ->map(function ($registry) { + $response = Http::withOptions([ + 'verify' => CaBundle::getSystemCaRootBundlePath(), + ])->get(trim($registry)); - return Arr::get(json_decode($body, true), 'packages', []); + if ($response->ok()) { + return $response->json()['packages']; + } else { + throw new Exception(trans('admin.plugins.market.connection-error', ['error' => $response->status()])); + } }) ->flatten(1); diff --git a/app/Http/Controllers/UpdateController.php b/app/Http/Controllers/UpdateController.php index 18db7296..cd38f07e 100644 --- a/app/Http/Controllers/UpdateController.php +++ b/app/Http/Controllers/UpdateController.php @@ -6,21 +6,19 @@ use App\Services\Unzip; use Cache; use Composer\CaBundle\CaBundle; use Composer\Semver\Comparator; -use Exception; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; use Illuminate\Contracts\Console\Kernel as Artisan; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Http; use Symfony\Component\Finder\SplFileInfo; class UpdateController extends Controller { const SPEC = 2; - public function showUpdatePage(Client $client) + public function showUpdatePage() { - $info = $this->getUpdateInfo($client); + $info = $this->getUpdateInfo(); $canUpdate = $this->canUpdate(Arr::get($info, 'info')); return view('admin.update', [ @@ -33,30 +31,30 @@ class UpdateController extends Controller ]); } - public function download(Unzip $unzip, Filesystem $filesystem, Client $client) + public function download(Unzip $unzip, Filesystem $filesystem) { - $info = $this->getUpdateInfo($client); + $info = $this->getUpdateInfo(); if (!$info['ok'] || !$this->canUpdate($info['info'])['can']) { return json(trans('admin.update.info.up-to-date'), 1); } $info = $info['info']; $path = tempnam(sys_get_temp_dir(), 'bs'); - try { - $client->get($info['url'], [ - 'sink' => $path, - 'verify' => CaBundle::getSystemCaRootBundlePath(), - ]); + + $response = Http::withOptions([ + 'sink' => $path, + 'verify' => CaBundle::getSystemCaRootBundlePath(), + ])->get($info['url']); + + if ($response->ok()) { $unzip->extract($path, base_path()); // Delete options cache. This allows us to update the version. $filesystem->delete(storage_path('options.php')); return json(trans('admin.update.complete'), 0); - } catch (Exception $e) { - report($e); - - return json(trans('admin.download.errors.download', ['error' => $e->getMessage()]), 1); + } else { + return json(trans('admin.download.errors.download', ['error' => $response->status()]), 1); } } @@ -82,21 +80,21 @@ class UpdateController extends Controller return view('setup.updates.success'); } - protected function getUpdateInfo(Client $client) + protected function getUpdateInfo() { - try { - $response = $client->request('GET', config('app.update_source'), [ - 'verify' => CaBundle::getSystemCaRootBundlePath(), - ]); - $info = json_decode($response->getBody(), true); + $response = Http::withOptions([ + 'verify' => CaBundle::getSystemCaRootBundlePath(), + ])->get(config('app.update_source')); + if ($response->ok()) { + $info = $response->json(); if (Arr::get($info, 'spec') === self::SPEC) { return ['ok' => true, 'info' => $info]; } else { return ['ok' => false, 'error' => trans('admin.update.errors.spec')]; } - } catch (RequestException $e) { - return ['ok' => false, 'error' => $e->getMessage()]; + } else { + return ['ok' => false, 'error' => 'HTTP status code: '.$response->status()]; } } diff --git a/app/Rules/Captcha.php b/app/Rules/Captcha.php index 8f65f643..33e9954d 100644 --- a/app/Rules/Captcha.php +++ b/app/Rules/Captcha.php @@ -2,38 +2,24 @@ namespace App\Rules; +use Composer\CaBundle\CaBundle; use Gregwar\Captcha\CaptchaBuilder; use Illuminate\Contracts\Validation\Rule; +use Illuminate\Support\Facades\Http; class Captcha implements Rule { - protected $client; - - public function __construct(\GuzzleHttp\Client $client) - { - $this->client = $client; - } - public function passes($attribute, $value) { $secretkey = option('recaptcha_secretkey'); if ($secretkey) { - try { - $response = $this->client->post('https://www.recaptcha.net/recaptcha/api/siteverify', [ - 'form_params' => [ - 'secret' => $secretkey, - 'response' => $value, - ], - 'verify' => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(), - ]); - if ($response->getStatusCode() == 200) { - $body = json_decode((string) $response->getBody()); - - return $body->success; - } - } catch (\GuzzleHttp\Exception\RequestException $e) { - return false; - } + return Http::asForm() + ->withOptions(['verify' => CaBundle::getSystemCaRootBundlePath()]) + ->post('https://www.recaptcha.net/recaptcha/api/siteverify', [ + 'secret' => $secretkey, + 'response' => $value, + ]) + ->json()['success']; } $builder = new CaptchaBuilder(session()->pull('captcha')); diff --git a/config/app.php b/config/app.php index 761cb3d3..37ca2fbe 100644 --- a/config/app.php +++ b/config/app.php @@ -217,6 +217,7 @@ return [ 'File' => Illuminate\Support\Facades\File::class, 'Gate' => Illuminate\Support\Facades\Gate::class, 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Http' => Illuminate\Support\Facades\Http::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, diff --git a/tests/Concerns/MocksGuzzleClient.php b/tests/Concerns/MocksGuzzleClient.php deleted file mode 100644 index 2a507f4b..00000000 --- a/tests/Concerns/MocksGuzzleClient.php +++ /dev/null @@ -1,69 +0,0 @@ -guzzleMockHandler = new MockHandler($responses); - $handler = HandlerStack::create($this->guzzleMockHandler); - $client = new Client(['handler' => $handler]); - - // Inject to Laravel service container - $this->app->instance(Client::class, $client); - } - - /** - * Add responses to Guzzle client's mock queue. - * Pass a Response or RequestException instance, or an array of them. - * - * @param array|Response|RequestException|int $response - * @param array $headers - * @param string $body - * @param string $version - * @param string|null $reason - */ - public function appendToGuzzleQueue($response = 200, $headers = [], $body = '', $version = '1.1', $reason = null) - { - if (!$this->guzzleMockHandler) { - $this->setupGuzzleClientMock(); - } - - if (is_array($response)) { - foreach ($response as $single) { - $this->appendToGuzzleQueue($single); - } - - return; - } - - if ($response instanceof Response || $response instanceof RequestException) { - return $this->guzzleMockHandler->append($response); - } - - return $this->guzzleMockHandler->append(new Response($response, $headers, $body, $version, $reason)); - } -} diff --git a/tests/HttpTest/ControllersTest/AuthControllerTest.php b/tests/HttpTest/ControllersTest/AuthControllerTest.php index f23b6ee4..581fb4f8 100644 --- a/tests/HttpTest/ControllersTest/AuthControllerTest.php +++ b/tests/HttpTest/ControllersTest/AuthControllerTest.php @@ -6,6 +6,7 @@ use App\Events; use App\Mail\ForgotPassword; use App\Models\Player; use App\Models\User; +use App\Rules\Captcha; use App\Services\Facades\Option; use Cache; use Event; @@ -25,11 +26,7 @@ class AuthControllerTest extends TestCase protected function setUp(): void { parent::setUp(); - app()->instance(\App\Rules\Captcha::class, new class() extends \App\Rules\Captcha { - public function __construct(\GuzzleHttp\Client $client = null) - { - } - + app()->instance(Captcha::class, new class() extends Captcha { public function passes($attribute, $value) { return true; diff --git a/tests/HttpTest/ControllersTest/MarketControllerTest.php b/tests/HttpTest/ControllersTest/MarketControllerTest.php index 6ad054ad..51272c94 100644 --- a/tests/HttpTest/ControllersTest/MarketControllerTest.php +++ b/tests/HttpTest/ControllersTest/MarketControllerTest.php @@ -5,15 +5,10 @@ namespace Tests; use App\Services\Plugin; use App\Services\PluginManager; use App\Services\Unzip; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Psr7\Request; -use GuzzleHttp\Psr7\Response; -use Tests\Concerns\MocksGuzzleClient; +use Illuminate\Support\Facades\Http; class MarketControllerTest extends TestCase { - use MocksGuzzleClient; - protected function setUp(): void { parent::setUp(); @@ -22,12 +17,37 @@ class MarketControllerTest extends TestCase public function testDownload() { - $this->setupGuzzleClientMock(); + Http::fake([ + config('plugins.registry') => Http::sequence() + ->push(['version' => 1, 'packages' => []]) + ->push([ + 'version' => 1, + 'packages' => [ + [ + 'name' => 'fake', + 'version' => '0.0.0', + 'require' => ['a' => '^4.0.0'], + ], + ], + ]) + ->whenEmpty([ + 'version' => 1, + 'packages' => [ + [ + 'name' => 'fake', + 'version' => '0.0.0', + 'dist' => [ + 'url' => 'http://nowhere.test/', + 'shasum' => 'deadbeef', + ], + ], + ], + ]), + 'http://nowhere.test/' => Http::sequence() + ->pushStatus(404) + ->pushStatus(200), + ]); - $this->appendToGuzzleQueue(200, [], json_encode([ - 'version' => 1, - 'packages' => [], - ])); $this->postJson('/admin/plugins/market/download', ['name' => 'nope']) ->assertJson([ 'code' => 1, @@ -35,14 +55,6 @@ class MarketControllerTest extends TestCase ]); // Unresolved plugin. - $fakeRegistry = json_encode(['packages' => [ - [ - 'name' => 'fake', - 'version' => '0.0.0', - 'require' => ['a' => '^4.0.0'], - ], - ]]); - $this->appendToGuzzleQueue([new Response(200, [], $fakeRegistry)]); $this->postJson('/admin/plugins/market/download', ['name' => 'fake']) ->assertJson([ 'message' => trans('admin.plugins.market.unresolved'), @@ -55,24 +67,9 @@ class MarketControllerTest extends TestCase ]); // Download - $fakeRegistry = json_encode(['packages' => [ - [ - 'name' => 'fake', - 'version' => '0.0.0', - 'dist' => ['url' => 'http://nowhere.test/', 'shasum' => 'deadbeef'], - ], - ]]); - $this->appendToGuzzleQueue([ - new Response(200, [], $fakeRegistry), - new Response(404), - ]); $this->postJson('/admin/plugins/market/download', ['name' => 'fake']) ->assertJson(['code' => 1]); - $this->appendToGuzzleQueue([ - new Response(200, [], $fakeRegistry), - new Response(200), - ]); $this->mock(Unzip::class, function ($mock) { $mock->shouldReceive('extract')->once(); }); @@ -85,29 +82,31 @@ class MarketControllerTest extends TestCase public function testMarketData() { - $this->setupGuzzleClientMock([ - new RequestException('Connection Error', new Request('POST', '')), - new Response(200, [], json_encode(['version' => 1, 'packages' => [ - [ - 'name' => 'fake1', - 'title' => 'Fake', - 'version' => '1.0.0', - 'description' => '', - 'author' => '', - 'dist' => [], - 'require' => [], + Http::fakeSequence() + ->pushStatus(404) + ->push([ + 'version' => 1, + 'packages' => [ + [ + 'name' => 'fake1', + 'title' => 'Fake', + 'version' => '1.0.0', + 'description' => '', + 'author' => '', + 'dist' => [], + 'require' => [], + ], + [ + 'name' => 'fake2', + 'title' => 'Fake', + 'version' => '0.0.0', + 'description' => '', + 'author' => '', + 'dist' => [], + 'require' => [], + ], ], - [ - 'name' => 'fake2', - 'title' => 'Fake', - 'version' => '0.0.0', - 'description' => '', - 'author' => '', - 'dist' => [], - 'require' => [], - ], - ]])), - ]); + ]); $this->getJson('/admin/plugins/market/list')->assertStatus(500); diff --git a/tests/HttpTest/ControllersTest/UpdateControllerTest.php b/tests/HttpTest/ControllersTest/UpdateControllerTest.php index ecd4ff42..198fdff8 100644 --- a/tests/HttpTest/ControllersTest/UpdateControllerTest.php +++ b/tests/HttpTest/ControllersTest/UpdateControllerTest.php @@ -3,63 +3,55 @@ namespace Tests; use App\Services\Unzip; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Psr7\Request; -use GuzzleHttp\Psr7\Response; use Illuminate\Contracts\Console\Kernel as Artisan; use Illuminate\Filesystem\Filesystem; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Support\Facades\Http; use Symfony\Component\Finder\SplFileInfo; -use Tests\Concerns\MocksGuzzleClient; class UpdateControllerTest extends TestCase { use DatabaseTransactions; - use MocksGuzzleClient; protected function setUp(): void { parent::setUp(); - $this->actAs('superAdmin'); + $this->actingAs(factory(\App\Models\User::class)->states('superAdmin')->create()); } public function testShowUpdatePage() { - $this->setupGuzzleClientMock(); + Http::fakeSequence() + ->pushStatus(404) + ->push($this->fakeUpdateInfo('8.9.3', ['spec' => 0])) + ->push($this->fakeUpdateInfo('8.9.3', ['php' => '100.0.0'])) + ->push($this->fakeUpdateInfo('8.9.3')); // Can't connect to update source - $this->appendToGuzzleQueue([ - new RequestException('Connection Error', new Request('GET', 'whatever')), - ]); $this->get('/admin/update')->assertSee(config('app.version')); // Missing `spec` field - $this->appendToGuzzleQueue([ - new Response(200, [], $this->mockFakeUpdateInfo('8.9.3', ['spec' => 0])), - ]); $this->get('/admin/update')->assertSee(trans('admin.update.errors.spec')); // Low PHP version - $this->appendToGuzzleQueue([ - new Response(200, [], $this->mockFakeUpdateInfo('8.9.3', ['php' => '100.0.0'])), - ]); $this->get('/admin/update')->assertSee(trans('admin.update.errors.php', ['version' => '100.0.0'])); // New version available - $this->appendToGuzzleQueue([ - new Response(200, [], $this->mockFakeUpdateInfo('8.9.3')), - ]); $this->get('/admin/update')->assertSee(config('app.version'))->assertSee('8.9.3'); } public function testDownload() { - $this->setupGuzzleClientMock(); + Http::fake([ + config('app.update_source') => Http::sequence() + ->push($this->fakeUpdateInfo('1.2.3')) + ->whenEmpty($this->fakeUpdateInfo('8.9.3')), + 'https://whatever.test/8.9.3/update.zip' => Http::sequence() + ->pushStatus(404) + ->pushStatus(200), + ]); // Already up-to-date - $this->appendToGuzzleQueue([ - new Response(200, [], $this->mockFakeUpdateInfo('1.2.3')), - ]); $this->postJson('/admin/update/download') ->assertJson([ 'code' => 1, @@ -67,12 +59,6 @@ class UpdateControllerTest extends TestCase ]); // Download - $this->appendToGuzzleQueue([ - new Response(200, [], $this->mockFakeUpdateInfo('8.9.3')), - new Response(404), - new Response(200, [], $this->mockFakeUpdateInfo('8.9.3')), - new Response(200), - ]); $this->postJson('/admin/update/download')->assertJson(['code' => 1]); $this->mock(Unzip::class, function ($mock) { $mock->shouldReceive('extract')->once()->andReturn(); @@ -130,13 +116,13 @@ class UpdateControllerTest extends TestCase $this->assertEquals('100.0.0', option('version')); } - protected function mockFakeUpdateInfo(string $version, $extra = []) + protected function fakeUpdateInfo(string $version, $extra = []) { - return json_encode(array_merge([ + return array_merge([ 'spec' => 2, 'php' => '7.2.5', 'latest' => $version, 'url' => "https://whatever.test/$version/update.zip", - ], $extra)); + ], $extra); } } diff --git a/tests/RulesTest/CaptchaTest.php b/tests/RulesTest/CaptchaTest.php index 06ef4d82..45001989 100644 --- a/tests/RulesTest/CaptchaTest.php +++ b/tests/RulesTest/CaptchaTest.php @@ -3,23 +3,21 @@ namespace Tests; use App\Rules\Captcha; -use GuzzleHttp\Client; -use GuzzleHttp\Handler\MockHandler; -use GuzzleHttp\HandlerStack; -use GuzzleHttp\Psr7\Response; +use Illuminate\Http\Client\Request; +use Illuminate\Support\Facades\Http; class CaptchaTest extends TestCase { public function testCharactersCaptcha() { session(['captcha' => 'abc']); - $rule = resolve(Captcha::class); + $rule = new Captcha(); $this->assertFalse($rule->passes('captcha', 'abcd')); $this->assertEquals(trans('validation.captcha'), $rule->message()); $this->assertNull(session('captcha')); session(['captcha' => 'abc']); - $rule = resolve(Captcha::class); + $rule = new Captcha(); $this->assertTrue($rule->passes('captcha', 'abc')); $this->assertNull(session('captcha')); } @@ -27,16 +25,18 @@ class CaptchaTest extends TestCase public function testRecaptcha() { option(['recaptcha_secretkey' => 'secret']); - $mock = new MockHandler([ - new Response(403), - new Response(200, [], json_encode(['success' => true])), - ]); - $handler = HandlerStack::create($mock); - $client = new Client(['handler' => $handler]); + Http::fake(Http::response(['success' => true])); - $rule = new Captcha($client); - $this->assertFalse($rule->passes('captcha', 'value')); + $rule = new Captcha(); $this->assertTrue($rule->passes('captcha', 'value')); $this->assertEquals(trans('validation.recaptcha'), $rule->message()); + Http::assertSent(function (Request $request) { + $this->assertEquals( + ['secret' => 'secret', 'response' => 'value'], + $request->data() + ); + + return true; + }); } }