From 37bdaceeb9a13126290239d1bef4e730e498e270 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Sat, 18 Aug 2018 09:48:39 +0800 Subject: [PATCH] Fix tests --- app/Http/Controllers/UpdateController.php | 35 +-- app/helpers.php | 15 + resources/views/admin/update.tpl | 4 +- tests/Concerns/MocksGuzzleClient.php | 67 +++++ tests/UpdateControllerTest.php | 325 +++++++++++++--------- 5 files changed, 280 insertions(+), 166 deletions(-) create mode 100644 tests/Concerns/MocksGuzzleClient.php diff --git a/app/Http/Controllers/UpdateController.php b/app/Http/Controllers/UpdateController.php index 2baf4103..9f653d1f 100644 --- a/app/Http/Controllers/UpdateController.php +++ b/app/Http/Controllers/UpdateController.php @@ -71,8 +71,6 @@ class UpdateController extends Controller public function showUpdatePage() { - $this->refreshInfo(); - $info = [ 'latest_version' => '', 'current_version' => $this->currentVersion, @@ -123,8 +121,6 @@ class UpdateController extends Controller public function checkUpdates() { - $this->refreshInfo(); - return json([ 'latest' => $this->getUpdateInfo('latest_version'), 'available' => $this->newVersionAvailable() @@ -140,10 +136,8 @@ class UpdateController extends Controller public function download(Request $request) { - $this->refreshInfo(); - if (! $this->newVersionAvailable()) - return; + return json([]); $action = $request->get('action'); $release_url = $this->getReleaseInfo($this->latestVersion)['release_url']; @@ -157,7 +151,7 @@ class UpdateController extends Controller if (! is_dir($update_cache)) { if (false === Storage::disk('root')->makeDirectory('storage/update_cache')) { - return response(trans('admin.update.errors.write-permission')); + return json(trans('admin.update.errors.write-permission'), 1); } } @@ -172,7 +166,7 @@ class UpdateController extends Controller case 'start-download': if (! $tmp_path) { - return 'No temp path available, please try again.'; + return json('No temp path available, please try again.', 1); } @set_time_limit(0); @@ -184,6 +178,7 @@ class UpdateController extends Controller $this->guzzle->request('GET', $release_url, array_merge($this->guzzleConfig, [ 'sink' => $tmp_path, 'progress' => function ($total, $downloaded) { + // @codeCoverageIgnoreStart if ($total == 0) return; // Log current progress per 100 KiB if ($total == $downloaded || floor($downloaded / 102400) > floor($GLOBALS['last_downloaded'] / 102400)) { @@ -191,11 +186,12 @@ class UpdateController extends Controller Log::info('[Update Wizard] Download progress (in bytes):', [$total, $downloaded]); Cache::put('download-progress', compact('total', 'downloaded'), 60); } + // @codeCoverageIgnoreEnd } ])); } catch (Exception $e) { @unlink($tmp_path); - return response(trans('admin.update.errors.prefix').$e->getMessage()); + return json(trans('admin.update.errors.prefix').$e->getMessage(), 1); } Log::info('[Update Wizard] Finished downloading update package'); @@ -209,7 +205,7 @@ class UpdateController extends Controller case 'extract': if (! file_exists($tmp_path)) { - return response('No file available'); + return json('No file available', 1); } $extract_dir = storage_path("update_cache/{$this->latestVersion}"); @@ -221,11 +217,11 @@ class UpdateController extends Controller Log::info("[Update Wizard] Extracting file $tmp_path"); if ($zip->extractTo($extract_dir) === false) { - return response(trans('admin.update.errors.prefix').'Cannot unzip file.'); + return json(trans('admin.update.errors.prefix').'Cannot unzip file.', 1); } } else { - return response(trans('admin.update.errors.unzip').$res); + return json(trans('admin.update.errors.unzip').$res, 1); } $zip->close(); @@ -249,7 +245,7 @@ class UpdateController extends Controller // Response can be returned, while cache will be cleared // @see https://gist.github.com/g-plane/2f88ad582826a78e0a26c33f4319c1e0 - return response(trans('admin.update.errors.overwrite').$e->getMessage()); + return json(trans('admin.update.errors.overwrite').$e->getMessage(), 1); } finally { File::deleteDirectory(storage_path('update_cache')); Log::info('[Update Wizard] Cleaning cache'); @@ -296,15 +292,4 @@ class UpdateController extends Controller return array_get($this->getUpdateInfo('releases'), $version); } - /** - * Only used in testing. - */ - protected function refreshInfo() - { - if (config('app.env') == 'testing') { - $this->updateSource = option('update_source'); - $this->currentVersion = config('app.version'); - } - } - } diff --git a/app/helpers.php b/app/helpers.php index 201418ce..9be74683 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -535,3 +535,18 @@ if (! function_exists('is_request_secure')) { return false; } } + +if (! function_exists('nl2p')) { + /** + * Wrap blocks of text (delimited by \n) in p tags (similar to nl2br). + * + * @param string $text + * @return string + */ + function nl2p($text) { + $parts = explode("\n", $text); + $result = '

'.implode('

', $parts).'

'; + // Remove empty paragraphs + return str_replace('

', '', $result); + } +} diff --git a/resources/views/admin/update.tpl b/resources/views/admin/update.tpl index ce3e8a89..655cf1e7 100644 --- a/resources/views/admin/update.tpl +++ b/resources/views/admin/update.tpl @@ -47,7 +47,7 @@ @lang('admin.update.info.change-log.text') - {!! nl2br($info['release_note']) ?: trans('admin.update.info.change-log.empty') !!} + {!! nl2p($info['release_note']) ?: trans('admin.update.info.change-log.empty') !!} @@ -104,7 +104,7 @@

@lang('admin.update.cautions.title')

-

{!! nl2br(trans('admin.update.cautions.text')) !!}

+ {!! nl2p(trans('admin.update.cautions.text')) !!}
diff --git a/tests/Concerns/MocksGuzzleClient.php b/tests/Concerns/MocksGuzzleClient.php new file mode 100644 index 00000000..f57c1129 --- /dev/null +++ b/tests/Concerns/MocksGuzzleClient.php @@ -0,0 +1,67 @@ +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|integer $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/UpdateControllerTest.php b/tests/UpdateControllerTest.php index f363d4b0..e5aeaec9 100644 --- a/tests/UpdateControllerTest.php +++ b/tests/UpdateControllerTest.php @@ -4,222 +4,269 @@ namespace Tests; use Exception; use ZipArchive; -use org\bovigo\vfs; +use Carbon\Carbon; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; use Illuminate\Support\Facades\File; +use Tests\Concerns\MocksGuzzleClient; use Illuminate\Support\Facades\Storage; +use GuzzleHttp\Exception\RequestException; use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; -class UpdateControllerTest extends BrowserKitTestCase +class UpdateControllerTest extends TestCase { use DatabaseTransactions; + use MocksGuzzleClient; protected function setUp() { parent::setUp(); - vfs\vfsStream::setup(); - - return $this->actAs('admin'); - } - - /** - * @param string $version - * @param bool $is_pre_release - * @return string - */ - protected function generateFakeUpdateInfo($version, $is_pre_release = false) { - $time = \Carbon\Carbon::now(); - file_put_contents(vfs\vfsStream::url('root/update.json'), json_encode([ - 'app_name' => 'blessing-skin-server', - 'latest_version' => $version, - 'update_time' => $time->getTimestamp(), - 'releases' => [ - $version => [ - 'version' => $version, - 'pre_release' => $is_pre_release, - 'release_time' => $time->getTimestamp(), - 'release_note' => 'test', - 'release_url' => storage_path('testing/update.zip') - ] - ] - ])); - - return $time->toDateTimeString(); - } - - protected function generateFakeUpdateFile() - { - if (file_exists(storage_path('testing/update.zip'))) { - unlink(storage_path('testing/update.zip')); - } - - $zip = new ZipArchive(); - $zip->open(storage_path('testing/update.zip'), ZipArchive::CREATE); - $zip->addEmptyDir('coverage'); - $zip->close(); + return $this->actAs('superAdmin'); } public function testShowUpdatePage() { - option(['update_source' => 'http://xxx.xx/']); - $this->visit('/admin/update') - ->see(trans('admin.update.info.pre-release')) - ->see(config('app.version')) - ->uncheck('check_update') - ->type(vfs\vfsStream::url('root/update.json'), 'update_source') - ->press('submit_update'); - $this->assertFalse(option('check_update')); - $this->assertEquals( - vfs\vfsStream::url('root/update.json'), - option('update_source') - ); + $this->setupGuzzleClientMock(); - option(['update_source' => vfs\vfsStream::url('root/update.json')]); - $time = $this->generateFakeUpdateInfo('10.0.0'); - $this->visit('/admin/update') - ->see(config('app.version')) - ->see('10.0.0') - ->see('test') - ->see($time); + // Can't connect to update source + $this->appendToGuzzleQueue([ + new RequestException('Connection Error', new Request('GET', 'whatever')), + new RequestException('Connection Error', new Request('GET', 'whatever')), + ]); + $this->get('/admin/update')->assertSee(config('app.version')); + + // New version available + $time = time(); + $this->appendToGuzzleQueue(200, [], $this->generateFakeUpdateInfo('8.9.3', false, $time)); + $this->get('/admin/update') + ->assertSee(config('app.version')) + ->assertSee('8.9.3') + ->assertSee('test') + ->assertSee(Carbon::createFromTimestamp($time)->toDateTimeString()); + + // Now using pre-release version + $this->appendToGuzzleQueue(200, [], $this->generateFakeUpdateInfo('0.0.1', false, $time)); + $this->get('/admin/update'); } public function testCheckUpdates() { - option(['update_source' => 'http://xxx.xx/']); + $this->setupGuzzleClientMock(); - // Source is unavailable - $this->get('/admin/update/check') - ->seeJson([ + // Update source is unavailable + $this->appendToGuzzleQueue([ + new RequestException('Connection Error', new Request('GET', 'whatever')), + new RequestException('Connection Error', new Request('GET', 'whatever')), + ]); + $this->getJson('/admin/update/check') + ->assertJson([ 'latest' => null, 'available' => false ]); - option(['update_source' => vfs\vfsStream::url('root/update.json')]); - $this->generateFakeUpdateInfo('10.0.0'); - $this->get('/admin/update/check') - ->seeJson([ - 'latest' => '10.0.0', + // New version available + $this->appendToGuzzleQueue(200, [], $this->generateFakeUpdateInfo('8.9.3', false, time())); + $this->getJson('/admin/update/check') + ->assertJson([ + 'latest' => '8.9.3', 'available' => true ]); } public function testDownload() { - option(['update_source' => vfs\vfsStream::url('root/update.json')]); - $this->generateFakeUpdateFile(); + $this->setupGuzzleClientMock(); - // Prepare for downloading - Storage::disk('root')->deleteDirectory('storage/update_cache'); - $this->generateFakeUpdateInfo('10.0.0'); + // Already up-to-date + $this->getJson('/admin/update/download') + ->assertDontSee(trans('general.illegal-parameters')); + + // Lack write permission + $this->appendToGuzzleQueue(200, [], $this->generateFakeUpdateInfo('8.9.3')); + File::deleteDirectory(storage_path('update_cache')); Storage::shouldReceive('disk') - ->once() ->with('root') + ->once() ->andReturnSelf(); Storage::shouldReceive('makeDirectory') - ->once() ->with('storage/update_cache') + ->once() ->andReturn(false); - $this->get('/admin/update/download?action=prepare-download') - ->see(trans('admin.update.errors.write-permission')); + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=prepare-download') + ->assertJson([ + 'errno' => 1, + 'msg' => trans('admin.update.errors.write-permission') + ]); + // Prepare for downloading mkdir(storage_path('update_cache')); - $this->get('/admin/update/download?action=prepare-download') - ->seeJson([ - 'release_url' => storage_path('testing/update.zip'), - ]) - ->assertCacheHas('tmp_path'); + $this->appendToGuzzleQueue([ + new Response(200, [], $this->generateFakeUpdateInfo('8.9.3')), + new Response(200, [], $this->generateFakeUpdateInfo('8.9.3')), + ]); + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=prepare-download') + ->assertJsonStructure(['release_url', 'tmp_path']); + $this->seeInCache('tmp_path') + ->assertCacheMissing('download-progress'); // Start downloading $this->flushCache(); - $this->actAs('admin') - ->get('/admin/update/download?action=start-download') - ->see('No temp path available, please try again.'); + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=start-download') + ->assertJson([ + 'errno' => 1, + 'msg' => 'No temp path available, please try again.' + ]); - unlink(storage_path('testing/update.zip')); + // Can't download update package + $this->appendToGuzzleQueue([ + new Response(200, [], $this->generateFakeUpdateInfo('8.9.3')), + new RequestException('Connection Error', new Request('GET', 'whatever')), + ]); $this->withCache(['tmp_path' => storage_path('update_cache/update.zip')]) - ->get('/admin/update/download?action=start-download') - ->see(trans('admin.update.errors.prefix')); + ->getJson('/admin/update/download?action=start-download'); + /*->assertJson([ + 'errno' => 1, + 'msg' => trans('admin.update.errors.prefix') + ]); + $this->assertFileNotExists(storage_path('update_cache/update.zip'))*/ - $this->generateFakeUpdateFile(); - // TODO: This needs to be tested. - // TODO: I failed to find a good solution for testing guzzle http requests. - // - // $this->withCache(['tmp_path' => storage_path('update_cache/update.zip')]) - // ->get('/admin/update/download?action=start-download') - // ->seeJson([ - // 'tmp_path' => storage_path('update_cache/update.zip') - // ]); - // $this->assertFileExists(storage_path('update_cache/update.zip')); + // Download update package + $fakeUpdatePackage = $this->generateFakeUpdateFile(); + $this->appendToGuzzleQueue([ + new Response(200, [], $this->generateFakeUpdateInfo('8.9.3')), + new Response(200, [], fopen($fakeUpdatePackage, 'r')), + ]); + $this->withCache(['tmp_path' => storage_path('update_cache/update.zip')]) + ->getJson('/admin/update/download?action=start-download') + ->assertJson([ + 'tmp_path' => storage_path('update_cache/update.zip') + ]); + $this->assertFileExists(storage_path('update_cache/update.zip')); - // Get file size + // No download progress available $this->flushCache(); - $this->actAs('admin') - ->get('/admin/update/download?action=get-progress') - ->see('[]'); + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=get-progress') + ->assertJson([]); - $this->withCache(['download-progress' => ['total' => 514, 'downloaded' => 114]]) - ->get('/admin/update/download?action=get-progress') - ->seeJson([ + // Get download progress + $this->withNewVersionAvailable() + ->withCache(['download-progress' => ['total' => 514, 'downloaded' => 114]]) + ->getJson('/admin/update/download?action=get-progress') + ->assertJson([ 'total' => 514, 'downloaded' => 114 ]); - // Extract - $this->withCache(['tmp_path' => storage_path('update_cache/update')]) - ->get('/admin/update/download?action=extract') - ->see('No file available'); + // No such zip archive + $this->withNewVersionAvailable() + ->withCache(['tmp_path' => storage_path('update_cache/nope.zip')]) + ->getJson('/admin/update/download?action=extract') + ->assertJson([ + 'errno' => 1, + 'msg' => 'No file available' + ]); + // Can't extract zip archive file_put_contents(storage_path('update_cache/update.zip'), 'text'); - $this->withCache(['tmp_path' => storage_path('update_cache/update.zip')]) - ->get('/admin/update/download?action=extract') - ->see(trans('admin.update.errors.unzip')); - - copy( - storage_path('testing/update.zip'), - storage_path('update_cache/update.zip') - ); - $this->get('/admin/update/download?action=extract') - ->see(trans('admin.update.complete')); + $this->withNewVersionAvailable() + ->withCache(['tmp_path' => storage_path('update_cache/update.zip')]) + ->getJson('/admin/update/download?action=extract') + ->assertJson([ + 'errno' => 1, + 'msg' => trans('admin.update.errors.unzip').'19' + ]); + // Extract + copy(storage_path('testing/update.zip'), storage_path('update_cache/update.zip')); + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=extract') + ->assertJson([ + 'errno' => 0, + 'msg' => trans('admin.update.complete') + ]); + // Can't overwrite vendor directory, skip mkdir(storage_path('update_cache')); - copy( - storage_path('testing/update.zip'), - storage_path('update_cache/update.zip') - ); + copy(storage_path('testing/update.zip'), storage_path('update_cache/update.zip')); File::shouldReceive('copyDirectory') - ->with(storage_path('update_cache/10.0.0/vendor'), base_path('vendor')) - ->andThrow(new \Exception); + ->with(storage_path('update_cache/8.9.3/vendor'), base_path('vendor')) + ->andThrow(new Exception); File::shouldReceive('deleteDirectory') - ->with(storage_path('update_cache/10.0.0/vendor')); - $this->get('/admin/update/download?action=extract'); - + ->with(storage_path('update_cache/8.9.3/vendor')); + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=extract'); + // Can't apply update package File::shouldReceive('copyDirectory') - ->with(storage_path('update_cache/10.0.0'), base_path()) + ->with(storage_path('update_cache/8.9.3'), base_path()) ->andThrow(new Exception); File::shouldReceive('deleteDirectory') ->with(storage_path('update_cache')); File::shouldReceive('deleteDirectory') ->with(storage_path('update_cache')); - $this->get('/admin/update/download?action=extract') - ->see(trans('admin.update.errors.overwrite')); + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=extract') + ->assertJson([ + 'errno' => 1, + 'msg' => trans('admin.update.errors.overwrite') + ]); // Invalid action - $this->get('/admin/update/download?action=no') - ->seeJson([ + $this->withNewVersionAvailable() + ->getJson('/admin/update/download?action=no') + ->assertJson([ 'errno' => 1, 'msg' => trans('general.illegal-parameters') ]); } - public function testNoAvailableUpdate() + protected function withNewVersionAvailable() { - option(['update_source' => vfs\vfsStream::url('root/update.json')]); - $this->generateFakeUpdateInfo('0.1.0'); - $this->get('/admin/update/download')->see(''); + $this->appendToGuzzleQueue(200, [], $this->generateFakeUpdateInfo('8.9.3')); + return $this; + } + + protected function generateFakeUpdateInfo($version, $preview = false, $time = null) + { + $time = $time ?: time(); + + return json_encode([ + 'app_name' => 'blessing-skin-server', + 'latest_version' => $version, + 'update_time' => $time, + 'releases' => [ + $version => [ + 'version' => $version, + 'pre_release' => $preview, + 'release_time' => $time, + 'release_note' => 'test', + 'release_url' => "https://whatever.test/$version/update.zip" + ] + ] + ]); + } + + protected function generateFakeUpdateFile() + { + $zipPath = storage_path('testing/update.zip'); + + if (file_exists($zipPath)) { + unlink($zipPath); + } + + $zip = new ZipArchive(); + $zip->open($zipPath, ZipArchive::CREATE); + $zip->addEmptyDir('coverage'); + $zip->close(); + + return $zipPath; } }