diff --git a/.gitignore b/.gitignore index 06024100..6f7ff152 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ plugins/* storage/textures/* storage/update_cache/* node_modules/* -resources/assets/src/bower_components/* diff --git a/app/Http/Controllers/UpdateController.php b/app/Http/Controllers/UpdateController.php index 39595e60..e9de1066 100644 --- a/app/Http/Controllers/UpdateController.php +++ b/app/Http/Controllers/UpdateController.php @@ -7,6 +7,7 @@ use Log; use Utils; use File; use Option; +use Storage; use ZipArchive; use App\Services\OptionForm; use Illuminate\Http\Request; @@ -114,8 +115,8 @@ class UpdateController extends Controller $update_cache = storage_path('update_cache'); if (!is_dir($update_cache)) { - if (false === mkdir($update_cache)) { - exit(trans('admin.update.errors.write-permission')); + if (false === Storage::disk('storage')->makeDirectory('update_cache')) { + return response(trans('admin.update.errors.write-permission')); } } @@ -125,8 +126,6 @@ class UpdateController extends Controller return json(compact('release_url', 'tmp_path', 'file_size')); - break; - case 'start-download': if (!session()->has('tmp_path')) return "No temp path is set."; @@ -137,13 +136,11 @@ class UpdateController extends Controller } catch (\Exception $e) { File::delete($tmp_path); - exit(trans('admin.update.errors.prefix').$e->getMessage()); + return response(trans('admin.update.errors.prefix').$e->getMessage()); } return json(compact('tmp_path')); - break; - case 'get-file-size': if (!session()->has('tmp_path')) return "No temp path is set."; @@ -152,11 +149,10 @@ class UpdateController extends Controller return json(['size' => filesize($tmp_path)]); } - break; - case 'extract': - if (!file_exists($tmp_path)) exit('No file available'); + if (!file_exists($tmp_path)) + return response('No file available'); $extract_dir = storage_path("update_cache/{$this->latestVersion}"); @@ -166,14 +162,12 @@ class UpdateController extends Controller if ($res === true) { Log::info("[ZipArchive] Extracting file $tmp_path"); - try { - $zip->extractTo($extract_dir); - } catch (\Exception $e) { - exit(trans('admin.update.errors.prefix').$e->getMessage()); + if ($zip->extractTo($extract_dir) === false) { + return response(trans('admin.update.errors.prefix').'Cannot unzip file.'); } } else { - exit(trans('admin.update.errors.unzip').$res); + return response(trans('admin.update.errors.unzip').$res); } $zip->close(); @@ -190,24 +184,22 @@ class UpdateController extends Controller File::copyDirectory($extract_dir, base_path()); Log::info("[Extracter] Covering files"); - File::deleteDirectory(storage_path('update_cache')); - - Log::info("[Extracter] Cleaning cache"); } catch (\Exception $e) { Log::error("[Extracter] Error occured when covering files", [$e]); + // 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()); + } finally { File::deleteDirectory(storage_path('update_cache')); - exit(trans('admin.update.errors.overwrite').$e->getMessage()); + Log::info("[Extracter] Cleaning cache"); } return json(trans('admin.update.complete'), 0); - break; - default: - # code... - break; + return json(trans('general.illegal-parameters'), 1); } } @@ -215,7 +207,9 @@ class UpdateController extends Controller { if (!$this->updateInfo) { // add timestamp to control cdn cache - $url = $this->updateSource."?v=".substr(time(), 0, -3); + $url = starts_with($this->updateSource, 'http') + ? $this->updateSource."?v=".substr(time(), 0, -3) + : $this->updateSource; try { $response = file_get_contents($url); diff --git a/tests/UpdateControllerTest.php b/tests/UpdateControllerTest.php new file mode 100644 index 00000000..c615e1c2 --- /dev/null +++ b/tests/UpdateControllerTest.php @@ -0,0 +1,212 @@ +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(storage_path('testing/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(); + } + + public function testShowUpdatePage() + { + option(['update_source' => 'http://xxx.xx/']); + $this->visit('/admin/update') + ->see(trans('admin.update.errors.connection')) + ->see(config('app.version')) + ->uncheck('check_update') + ->type(storage_path('testing/update.json'), 'update_source') + ->press('submit_update'); + $this->assertFalse(option('check_update')); + $this->assertEquals( + storage_path('testing/update.json'), + option('update_source') + ); + + $time = $this->generateFakeUpdateInfo('4.0.0'); + $this->visit('/admin/update') + ->see(config('app.version')) + ->see('4.0.0') + ->see('test') + ->see($time); + + file_put_contents(storage_path('testing/update.json'), json_encode([ + 'latest_version' => '4.0.0' + ])); + $this->visit('/admin/update') + ->see(trans('admin.update.info.pre-release')); + } + + public function testCheckUpdates() + { + option(['update_source' => 'http://xxx.xx/']); + + // Source is unavailable + $this->get('/admin/update/check') + ->seeJson([ + 'latest' => null, + 'available' => false + ]); + + option(['update_source' => storage_path('testing/update.json')]); + $this->generateFakeUpdateInfo('4.0.0'); + $this->get('/admin/update/check') + ->seeJson([ + 'latest' => '4.0.0', + 'available' => true + ]); + } + + public function testDownload() + { + option(['update_source' => storage_path('testing/update.json')]); + $this->generateFakeUpdateInfo('0.1.0'); + $this->get('/admin/update/download'); + + $this->generateFakeUpdateFile(); + + // Prepare for downloading + Storage::disk('storage')->deleteDirectory('update_cache'); + $this->generateFakeUpdateInfo('4.0.0'); + Storage::shouldReceive('disk') + ->with('storage') + ->once() + ->andReturnSelf(); + Storage::shouldReceive('makeDirectory') + ->with('update_cache') + ->once() + ->andReturn(false); + $this->get('/admin/update/download?action=prepare-download') + ->see(trans('admin.update.errors.write-permission')); + + mkdir(storage_path('update_cache')); + $this->get('/admin/update/download?action=prepare-download') + ->seeJson([ + 'release_url' => storage_path('testing/update.zip'), + 'file_size' => filesize(storage_path('testing/update.zip')) + ]) + ->assertSessionHas('tmp_path'); + + // Start downloading + $this->flushSession(); + $this->actAs('admin') + ->get('/admin/update/download?action=start-download') + ->see('No temp path is set.'); + + unlink(storage_path('testing/update.zip')); + $this->withSession(['tmp_path' => storage_path('update_cache/update.zip')]) + ->get('/admin/update/download?action=start-download') + ->see(trans('admin.update.errors.prefix')); + + $this->generateFakeUpdateFile(); + $this->get('/admin/update/download?action=start-download') + ->seeJson([ + 'tmp_path' => storage_path('update_cache/update.zip') + ]); + $this->assertFileExists(storage_path('update_cache/update.zip')); + + // Get file size + $this->flushSession(); + $this->actAs('admin') + ->get('/admin/update/download?action=get-file-size') + ->see('No temp path is set.'); + + $this->withSession(['tmp_path' => storage_path('update_cache/update.zip')]) + ->get('/admin/update/download?action=get-file-size') + ->seeJson([ + 'size' => filesize(storage_path('testing/update.zip')) + ]); + + // Extract + $this->withSession(['tmp_path' => storage_path('update_cache/update')]) + ->get('/admin/update/download?action=extract') + ->see('No file available'); + + file_put_contents(storage_path('update_cache/update.zip'), 'text'); + $this->withSession(['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')); + + + mkdir(storage_path('update_cache')); + copy( + storage_path('testing/update.zip'), + storage_path('update_cache/update.zip') + ); + File::shouldReceive('copyDirectory') + ->with(storage_path('update_cache/4.0.0/vendor'), base_path('vendor')) + ->andThrow(new \Exception); + File::shouldReceive('deleteDirectory') + ->with(storage_path('update_cache/4.0.0/vendor')); + $this->get('/admin/update/download?action=extract'); + + + File::shouldReceive('copyDirectory') + ->with(storage_path('update_cache/4.0.0'), 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')); + + // Invalid action + $this->get('/admin/update/download?action=no') + ->seeJson([ + 'errno' => 1, + 'msg' => trans('general.illegal-parameters') + ]); + } +}