diff --git a/app/Http/Controllers/MarketController.php b/app/Http/Controllers/MarketController.php
index 23145032..9fa9f3db 100644
--- a/app/Http/Controllers/MarketController.php
+++ b/app/Http/Controllers/MarketController.php
@@ -8,6 +8,7 @@ use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Services\PluginManager;
use Composer\Semver\Comparator;
+use App\Services\PackageManager;
class MarketController extends Controller
{
@@ -73,7 +74,7 @@ class MarketController extends Controller
]);
}
- public function download(Request $request, PluginManager $manager)
+ public function download(Request $request, PluginManager $manager, PackageManager $package)
{
$name = $request->get('name');
$metadata = $this->getPluginMetadata($name);
@@ -82,43 +83,16 @@ class MarketController extends Controller
return json(trans('admin.plugins.market.non-existent', ['plugin' => $name]), 1);
}
- // Gather plugin distribution URL
$url = $metadata['dist']['url'];
$filename = Arr::last(explode('/', $url));
- $plugins_dir = $manager->getPluginsDir();
- $tmp_path = $plugins_dir.DIRECTORY_SEPARATOR.$filename;
+ $pluginsDir = $manager->getPluginsDir();
+ $path = storage_path("packages/$filename");
- // Download
try {
- $this->guzzle->request('GET', $url, ['sink' => $tmp_path]);
+ $package->download($url, $path, $metadata['dist']['shasum'])->extract($pluginsDir);
} catch (Exception $e) {
- report($e);
-
- return json(trans('admin.plugins.market.download-failed', ['error' => $e->getMessage()]), 2);
+ return json($e->getMessage(), 1);
}
-
- // Check file's sha1 hash
- if (sha1_file($tmp_path) !== $metadata['dist']['shasum']) {
- @unlink($tmp_path);
-
- return json(trans('admin.plugins.market.shasum-failed'), 3);
- }
-
- // Unzip
- $zip = new ZipArchive();
- $res = $zip->open($tmp_path);
-
- if ($res === true) {
- if ($zip->extractTo($plugins_dir) === false) {
- return json(trans('admin.plugins.market.unzip-failed', ['error' => 'Unable to extract the file.']), 4);
- }
- $manager->copyPluginAssets(plugin($name));
- } else {
- return json(trans('admin.plugins.market.unzip-failed', ['error' => $res]), 4);
- }
- $zip->close();
- @unlink($tmp_path);
-
return json(trans('admin.plugins.market.install-success'), 0);
}
diff --git a/app/Http/Controllers/UpdateController.php b/app/Http/Controllers/UpdateController.php
index b22f6f1f..86e20e46 100644
--- a/app/Http/Controllers/UpdateController.php
+++ b/app/Http/Controllers/UpdateController.php
@@ -11,6 +11,7 @@ use ZipArchive;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use Composer\Semver\Comparator;
+use App\Services\PackageManager;
class UpdateController extends Controller
{
@@ -49,23 +50,12 @@ class UpdateController extends Controller
*/
protected $guzzle;
- /**
- * Default request options for Guzzle HTTP client.
- *
- * @var array
- */
- protected $guzzleConfig;
-
public function __construct(\GuzzleHttp\Client $guzzle)
{
$this->updateSource = config('app.update_source');
$this->currentVersion = config('app.version');
$this->guzzle = $guzzle;
- $this->guzzleConfig = [
- 'headers' => ['User-Agent' => config('secure.user_agent')],
- 'verify' => config('secure.certificates'),
- ];
}
public function showUpdatePage()
@@ -110,7 +100,7 @@ class UpdateController extends Controller
$connectivity = true;
try {
- $this->guzzle->request('GET', $this->updateSource, $this->guzzleConfig);
+ $this->guzzle->request('GET', $this->updateSource);
} catch (Exception $e) {
$connectivity = $e->getMessage();
}
@@ -134,129 +124,25 @@ class UpdateController extends Controller
return Comparator::greaterThan($latest, $this->currentVersion) && $this->getReleaseInfo($latest);
}
- public function download(Request $request)
+ public function download(Request $request, PackageManager $package)
{
if (! $this->newVersionAvailable()) {
return json([]);
}
- $action = $request->get('action');
- $release_url = $this->getReleaseInfo($this->latestVersion)['release_url'];
- $tmp_path = Cache::get('tmp_path');
-
- switch ($action) {
- case 'prepare-download':
-
- Cache::forget('download-progress');
- $update_cache = storage_path('update_cache');
-
- if (! is_dir($update_cache)) {
- if (false === Storage::disk('root')->makeDirectory('storage/update_cache')) {
- return json(trans('admin.update.errors.write-permission'), 1);
- }
- }
-
- // Set temporary path for the update package
- $tmp_path = $update_cache.'/update_'.time().'.zip';
- Cache::put('tmp_path', $tmp_path, 3600);
- Log::info('[Update Wizard] Prepare to download update package', compact('release_url', 'tmp_path'));
-
- // We won't get remote file size here since HTTP HEAD method is not always reliable
- return json(compact('release_url', 'tmp_path'));
-
- case 'start-download':
-
- if (! $tmp_path) {
- return json('No temp path available, please try again.', 1);
- }
-
- @set_time_limit(0);
- $GLOBALS['last_downloaded'] = 0;
-
- Log::info('[Update Wizard] Start downloading update package');
-
+ $url = $this->getReleaseInfo($this->latestVersion)['release_url'];
+ $path = storage_path('packages/bs_'.$this->latestVersion.'.zip');
+ switch ($request->get('action')) {
+ case 'download':
try {
- $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)) {
- $GLOBALS['last_downloaded'] = $downloaded;
- Log::info('[Update Wizard] Download progress (in bytes):', [$total, $downloaded]);
- Cache::put('download-progress', compact('total', 'downloaded'), 3600);
- }
- // @codeCoverageIgnoreEnd
- },
- ]));
- } catch (Exception $e) {
- @unlink($tmp_path);
-
- return json(trans('admin.update.errors.prefix').$e->getMessage(), 1);
- }
-
- Log::info('[Update Wizard] Finished downloading update package');
-
- return json(compact('tmp_path'));
-
- case 'get-progress':
-
- return json((array) Cache::get('download-progress'));
-
- case 'extract':
-
- if (! file_exists($tmp_path)) {
- return json('No file available', 1);
- }
-
- $extract_dir = storage_path("update_cache/{$this->latestVersion}");
-
- $zip = new ZipArchive();
- $res = $zip->open($tmp_path);
-
- if ($res === true) {
- Log::info("[Update Wizard] Extracting file $tmp_path");
-
- if ($zip->extractTo($extract_dir) === false) {
- return json(trans('admin.update.errors.prefix').'Cannot unzip file.', 1);
- }
- } else {
- return json(trans('admin.update.errors.unzip').$res, 1);
- }
- $zip->close();
-
- try {
- File::copyDirectory("$extract_dir/vendor", base_path('vendor'));
+ $package->download($url, $path)->extract(base_path());
+ return json(trans('admin.update.complete'), 0);
} catch (Exception $e) {
report($e);
- Log::error('[Update Wizard] Unable to extract vendors');
- // Skip copying vendor
- File::deleteDirectory("$extract_dir/vendor");
+ return json($e->getMessage(), 1);
}
-
- try {
- File::copyDirectory($extract_dir, base_path());
-
- Log::info('[Update Wizard] Overwrite with extracted files');
- } catch (Exception $e) {
- report($e);
- Log::error('[Update Wizard] Error occured when overwriting files');
-
- // Response can be returned, while cache will be cleared
- // @see https://gist.github.com/g-plane/2f88ad582826a78e0a26c33f4319c1e0
- return json(trans('admin.update.errors.overwrite').$e->getMessage(), 1);
- } finally {
- File::deleteDirectory(storage_path('update_cache'));
- Log::info('[Update Wizard] Cleaning cache');
- }
-
- Log::info('[Update Wizard] Done');
-
- return json(trans('admin.update.complete'), 0);
-
+ case 'progress':
+ return $package->progress();
default:
return json(trans('general.illegal-parameters'), 1);
}
@@ -271,7 +157,7 @@ class UpdateController extends Controller
: $this->updateSource;
try {
- $response = $this->guzzle->request('GET', $url, $this->guzzleConfig)->getBody();
+ $response = $this->guzzle->request('GET', $url)->getBody();
} catch (Exception $e) {
Log::error('[CheckingUpdate] Failed to get update information: '.$e->getMessage());
}
diff --git a/app/Services/PackageManager.php b/app/Services/PackageManager.php
new file mode 100644
index 00000000..df9cf4f9
--- /dev/null
+++ b/app/Services/PackageManager.php
@@ -0,0 +1,68 @@
+guzzle = $guzzle;
+ $this->onProgress = function ($total, $done) {
+ Cache::put($this->cacheKey, serialize(['total' => $total, 'done' => $done]));
+ };
+ }
+
+ public function download($url, $path, $shasum = null)
+ {
+ $this->path = $path;
+ $this->cacheKey = "download_$url";
+ Cache::forget($this->cacheKey);
+ try {
+ $this->guzzle->request('GET', $url, [
+ 'sink' => $path,
+ 'progress' => $this->onProgress
+ ]);
+ } catch (Exception $e) {
+ throw new Exception(trans('admin.download.errors.download', ['error' => $e->getMessage()]));
+ }
+
+ Cache::forget($this->cacheKey);
+ if (is_string($shasum) && sha1_file($path) != $shasum) {
+ @unlink($path);
+ throw new Exception(trans('admin.download.errors.shasum'));
+ }
+
+ return $this;
+ }
+
+ public function extract($destination)
+ {
+ $zip = new \ZipArchive();
+ $resource = $zip->open($this->path);
+
+ if ($resource === true && $zip->extractTo($destination)) {
+ $zip->close();
+ @unlink($this->path);
+ } else {
+ throw new Exception(trans('admin.download.errors.unzip'));
+ }
+ }
+
+ public function progress()
+ {
+ $progress = unserialize(Cache::get($this->cacheKey));
+ if ($progress['total'] == 0) {
+ return 0;
+ } else {
+ return $progress['done'] / $progress['total'];
+ }
+ }
+}
diff --git a/resources/assets/src/views/admin/Update.vue b/resources/assets/src/views/admin/Update.vue
index be775a7b..1f02e22d 100644
--- a/resources/assets/src/views/admin/Update.vue
+++ b/resources/assets/src/views/admin/Update.vue
@@ -7,11 +7,11 @@
@click="update"
>{{ $t('admin.updateButton') }}
- {{ $t('admin.preparing') }}
+ {{ $t('admin.downloading') }}
-
{{ $t('admin.updateSize') }}{{ total }} KB
- {{ ~~percentage }}%
+ {{ percentage }}%
@@ -55,54 +49,35 @@ export default {
data: () => ({
canUpdate: blessing.extra.canUpdate,
updating: false,
- total: 0,
- downloaded: 0,
+ percentage: 0,
}),
- computed: {
- percentage() {
- return this.downloaded / this.total * 100
- },
- },
methods: {
async update() {
this.updating = true
-
- await this.takeAction('prepare-download')
-
- this.updating && $('#modal-start-download').modal({
+ $('#modal-download').modal({
backdrop: 'static',
keyboard: false,
})
- setTimeout(this.polling, POLLING_INTERVAL)
-
- this.updating && await this.takeAction('start-download')
-
- this.updating && await this.takeAction('extract')
-
+ setTimeout(() => this.polling(), POLLING_INTERVAL)
+ const { errno, msg } = await this.$http.post(
+ '/admin/update/download',
+ { action: 'download' }
+ )
this.updating = false
- if (this.downloaded) {
- await this.$alert(this.$t('admin.updateCompleted'), { type: 'success' })
- window.location = blessing.base_url
- }
- },
- async takeAction(action) {
- const { errno, msg } = await this.$http.post('/admin/update/download', {
- action,
- })
if (errno) {
this.$alert(msg, { type: 'error' })
- this.updating = false
+ return
}
+ await this.$alert(this.$t('admin.updateCompleted'), { type: 'success' })
+ window.location = blessing.base_url
},
async polling() {
- const { downloaded, total } = await this.$http.get(
+ const percentage = await this.$http.get(
'/admin/update/download',
- { action: 'get-progress' }
+ { action: 'progress' }
)
- this.downloaded = ~~(+downloaded / 1024)
- this.total = ~~(+total / 1024)
-
+ this.percentage = ~~percentage * 100
this.updating && setTimeout(this.polling, POLLING_INTERVAL)
},
},
diff --git a/resources/assets/tests/views/admin/Update.test.ts b/resources/assets/tests/views/admin/Update.test.ts
index 8c8c7899..8bbaec94 100644
--- a/resources/assets/tests/views/admin/Update.test.ts
+++ b/resources/assets/tests/views/admin/Update.test.ts
@@ -17,34 +17,35 @@ test('perform update', async () => {
modal() {},
}))
Vue.prototype.$http.post
- .mockResolvedValueOnce({ errno: 1 })
+ .mockResolvedValueOnce({ errno: 1, msg: 'fail' })
.mockResolvedValue({})
- Vue.prototype.$http.get
- .mockResolvedValue({ total: 2048, downloaded: 2048 })
const wrapper = mount(Update)
const button = wrapper.find('button')
button.trigger('click')
await flushPromises()
- expect($).not.toBeCalled()
- expect(Vue.prototype.$http.post).toBeCalledWith(
- '/admin/update/download',
- { action: 'prepare-download' }
- )
+ expect(Vue.prototype.$alert).toBeCalledWith('fail', { type: 'error' })
+
button.trigger('click')
jest.runOnlyPendingTimers()
await flushPromises()
expect($).toBeCalled()
expect(Vue.prototype.$http.get).toBeCalledWith(
'/admin/update/download',
- { action: 'get-progress' }
+ { action: 'progress' }
)
expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/update/download',
- { action: 'start-download' }
- )
- expect(Vue.prototype.$http.post).toBeCalledWith(
- '/admin/update/download',
- { action: 'extract' }
+ { action: 'download' }
+ )
+})
+
+test('polling for querying download progress', async () => {
+ const wrapper = mount
}>(Update)
+ wrapper.setData({ updating: true })
+ await wrapper.vm.polling()
+ expect(Vue.prototype.$http.get).toBeCalledWith(
+ '/admin/update/download',
+ { action: 'progress' }
)
})
diff --git a/resources/lang/en/admin.yml b/resources/lang/en/admin.yml
index c609e71a..03a68115 100644
--- a/resources/lang/en/admin.yml
+++ b/resources/lang/en/admin.yml
@@ -91,11 +91,8 @@ plugins:
not-found: No such plugin.
market:
- connection-error: Unable to connect to the plugins registry (to change the plugins registry URL, please refer to http://t.cn/Rk6X37l). :error
+ connection-error: Unable to connect to the plugins registry. :error
non-existent: The plugin :plugin does not exist.
- download-failed: Unable to download the plugin. :error
- shasum-failed: The downloaded file failed hash check, please retry.
- unzip-failed: Unable to extract the plugin. :error
install-success: Plugin was installed.
empty: No result
@@ -140,11 +137,13 @@ update:
size: "Size of package:"
errors:
- prefix: "An error occured: "
connection: "Unable to access to current update source. Details:"
- write-permission: Unable to create the cache directory. Please check the permission.
- unzip: "Failed to extract update package. Error code: "
- overwrite: Unable to overwrite files.
+
+download:
+ errors:
+ download: 'Failed to download. Error: :error'
+ shasum: File validation failed. Please download again.
+ unzip: Failed to unpack files.
report-reviewed: This report has been processed.
diff --git a/resources/lang/en/front-end.yml b/resources/lang/en/front-end.yml
index f312484d..8f06b1ff 100644
--- a/resources/lang/en/front-end.yml
+++ b/resources/lang/en/front-end.yml
@@ -294,9 +294,7 @@ admin:
enabling it may cause unexpected problems. Do you really want to enable the
plugin?
updateButton: Update Now
- updateSize: "Size of package:"
- preparing: Preparing
- downloading: Downloading update package...
+ downloading: Downloading...
updateCompleted: Update completed.
change-color:
title: Change theme color
diff --git a/resources/lang/zh_CN/admin.yml b/resources/lang/zh_CN/admin.yml
index 56ed6bcc..c7da2db2 100644
--- a/resources/lang/zh_CN/admin.yml
+++ b/resources/lang/zh_CN/admin.yml
@@ -96,11 +96,8 @@ plugins:
not-found: 插件不存在
market:
- connection-error: 无法连接至插件市场源(更换市场源请参考:http://t.cn/Rk6X37l),错误信息::error
+ connection-error: 无法连接至插件市场源,错误信息::error
non-existent: 插件 :plugin 不存在
- download-failed: 插件下载失败,错误信息::error
- shasum-failed: 文件校验失败,请尝试重新下载
- unzip-failed: 插件解压缩失败,错误信息::error
install-success: 插件安装成功
empty: 无结果
@@ -145,11 +142,13 @@ update:
size: 更新包大小:
errors:
- prefix: 发生错误:
connection: 无法访问当前更新源。详细信息:
- write-permission: 你的服务器不支持自动更新:创建下载缓存文件夹失败,请检查目录权限。
- unzip: 更新包解压缩失败。错误代码:
- overwrite: 你的服务器不支持自动更新:无法覆盖文件。
+
+download:
+ errors:
+ download: 下载失败。原因::error
+ shasum: 文件校验失败,请重新下载。
+ unzip: 解压失败。
report-reviewed: 这一条举报已经处理过了。
diff --git a/resources/lang/zh_CN/front-end.yml b/resources/lang/zh_CN/front-end.yml
index 186a8288..5a130087 100644
--- a/resources/lang/zh_CN/front-end.yml
+++ b/resources/lang/zh_CN/front-end.yml
@@ -288,8 +288,6 @@ admin:
此插件没有声明任何依赖关系,这代表它有可能并不兼容此版本的 Blessing
Skin,请将此插件升级至可能的最新版本。强行启用可能导致无法预料的后果。你确定要启用此插件吗?
updateButton: 马上升级
- updateSize: 更新包大小:
- preparing: 正在准备
downloading: 正在下载更新包
updateCompleted: 更新完成
change-color:
diff --git a/storage/packages/.gitignore b/storage/packages/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/storage/packages/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/tests/Concerns/FakePackageManager.php b/tests/Concerns/FakePackageManager.php
new file mode 100644
index 00000000..60cb8f57
--- /dev/null
+++ b/tests/Concerns/FakePackageManager.php
@@ -0,0 +1,33 @@
+guzzle = $guzzle;
+ $this->throw = $throw;
+ }
+
+ public function download($url, $path, $shasum = null)
+ {
+ if ($this->throw) {
+ throw new \Exception('');
+ } else {
+ return $this;
+ }
+ }
+
+ public function extract($destination)
+ {
+ return true;
+ }
+
+ public function progress()
+ {
+ return '0';
+ }
+}
diff --git a/tests/MarketControllerTest.php b/tests/MarketControllerTest.php
index 28444915..a2950c62 100644
--- a/tests/MarketControllerTest.php
+++ b/tests/MarketControllerTest.php
@@ -4,6 +4,7 @@ namespace Tests;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
+use App\Services\PackageManager;
use Illuminate\Support\Facades\File;
use Tests\Concerns\MocksGuzzleClient;
use Tests\Concerns\GeneratesFakePlugins;
@@ -33,79 +34,19 @@ class MarketControllerTest extends TestCase
'msg' => trans('admin.plugins.market.non-existent', ['plugin' => 'non-existent-plugin']),
]);
- // Can't download due to connection error
- $this->appendToGuzzleQueue([
- new Response(200, [], $this->generateFakePluginsRegistry('fake-test-download', '0.0.1')),
- new RequestException('Connection Error', new Request('GET', 'whatever')),
- ]);
+ // Download
+ $fakeRegistry = $this->generateFakePluginsRegistry('fake-test-download', '0.0.1');
+ $this->appendToGuzzleQueue([new Response(200, [], $fakeRegistry)]);
+ app()->instance(PackageManager::class, new Concerns\FakePackageManager(null, true));
$this->postJson('/admin/plugins/market/download', [
'name' => 'fake-test-download',
- ])->assertJson([
- 'errno' => 2,
- 'msg' => trans('admin.plugins.market.download-failed', ['error' => 'Connection Error']),
- ]);
+ ])->assertJson(['errno' => 1]);
- // Downloaded plugin archive was tampered
- $fakeArchive = $this->generateFakePluginArchive(['name' => 'fake-test-download', 'version' => '0.0.1']);
- $this->appendToGuzzleQueue([
- new Response(200, [], $this->generateFakePluginsRegistry('fake-test-download', '0.0.1')),
- new Response(200, [], fopen($fakeArchive, 'r')),
- ]);
+ $this->appendToGuzzleQueue([new Response(200, [], $fakeRegistry)]);
+ app()->bind(PackageManager::class, Concerns\FakePackageManager::class);
$this->postJson('/admin/plugins/market/download', [
'name' => 'fake-test-download',
- ])->assertJson([
- 'errno' => 3,
- 'msg' => trans('admin.plugins.market.shasum-failed'),
- ]);
-
- // Download and extract plugin
- $shasum = sha1_file($fakeArchive);
- $this->appendToGuzzleQueue([
- new Response(200, [], $this->generateFakePluginsRegistry([
- [
- 'name' => 'fake-test-download',
- 'version' => '0.0.1',
- 'dist' => [
- 'url' => 'whatever',
- 'shasum' => $shasum,
- ],
- ],
- ])),
- new Response(200, [], fopen($fakeArchive, 'r')),
- ]);
- $this->postJson('/admin/plugins/market/download', [
- 'name' => 'fake-test-download',
- ])->assertJson([
- 'errno' => 0,
- 'msg' => trans('admin.plugins.market.install-success'),
- ]);
- $this->assertTrue(is_dir(config('plugins.directory').DIRECTORY_SEPARATOR.'fake-test-download'));
- $this->assertTrue(
- empty(glob(config('plugins.directory').DIRECTORY_SEPARATOR.'fake-test-download_*.zip'))
- );
-
- // Broken archive
- file_put_contents($fakeArchive, 'broken');
- $shasum = sha1_file($fakeArchive);
- $this->appendToGuzzleQueue([
- new Response(200, [], $this->generateFakePluginsRegistry([
- [
- 'name' => 'fake-test-download',
- 'version' => '0.0.1',
- 'dist' => [
- 'url' => 'whatever',
- 'shasum' => $shasum,
- ],
- ],
- ])),
- new Response(200, [], fopen($fakeArchive, 'r')),
- ]);
- $this->postJson('/admin/plugins/market/download', [
- 'name' => 'fake-test-download',
- ])->assertJson([
- 'errno' => 4,
- 'msg' => trans('admin.plugins.market.unzip-failed', ['error' => 19]),
- ]);
+ ])->assertJson(['errno' => 0, 'msg' => trans('admin.plugins.market.install-success')]);
}
public function testCheckUpdates()
diff --git a/tests/ServicesTest/PackageManagerTest.php b/tests/ServicesTest/PackageManagerTest.php
new file mode 100644
index 00000000..671f5687
--- /dev/null
+++ b/tests/ServicesTest/PackageManagerTest.php
@@ -0,0 +1,89 @@
+ $handler]);
+
+ $package = new PackageManager($client);
+ $this->assertInstanceOf(
+ PackageManager::class,
+ $package->download('url', storage_path('packages/temp'))
+ );
+
+ $this->expectException(Exception::class);
+ $package->download('url', storage_path('packages/temp'), 'deadbeef');
+
+ $this->expectException(Exception::class);
+ $package->download('url', storage_path('packages/temp'));
+ }
+
+ public function testExtract()
+ {
+ $mock = new MockHandler([new Response(200, [], 'contents')]);
+ $handler = HandlerStack::create($mock);
+ $client = new Client(['handler' => $handler]);
+ $package = new PackageManager($client);
+ $path = storage_path('packages/temp.zip');
+
+ $package->download('url', $path);
+ $zip = new ZipArchive();
+ $this->assertTrue($zip->open($path, ZipArchive::OVERWRITE));
+ $this->assertTrue($zip->addEmptyDir('zip-test'));
+ $zip->close();
+ $package->extract(storage_path('testing'));
+
+ $this->expectException(Exception::class);
+ $package->download('url', $path)->extract(storage_path('testing'));
+ }
+
+ public function testProgress()
+ {
+ $package = new PackageManager(new Client());
+ $reflect = new ReflectionClass($package);
+ $property = $reflect->getProperty('cacheKey');
+ $property->setAccessible(true);
+ $property->setValue($package, 'key');
+
+ Cache::put('key', serialize(['total' => 0, 'done' => 0]));
+ $this->assertEquals(0, $package->progress());
+
+ Cache::put('key', serialize(['total' => 2, 'done' => 1]));
+ $this->assertEquals(0.5, $package->progress());
+ }
+
+ public function testOnProgress()
+ {
+ $package = new PackageManager(new Client());
+ $reflect = new ReflectionClass($package);
+ $property = $reflect->getProperty('cacheKey');
+ $property->setAccessible(true);
+ $property->setValue($package, 'key');
+ $closure = $reflect->getProperty('onProgress');
+ $closure->setAccessible(true);
+
+ Cache::shouldReceive('put')->with('key', serialize(['total' => 5, 'done' => 4]));
+ call_user_func($closure->getValue($package), 5, 4);
+ }
+}
diff --git a/tests/UpdateControllerTest.php b/tests/UpdateControllerTest.php
index 3a748204..d0da973f 100644
--- a/tests/UpdateControllerTest.php
+++ b/tests/UpdateControllerTest.php
@@ -4,10 +4,10 @@ namespace Tests;
use Cache;
use Exception;
-use ZipArchive;
use Carbon\Carbon;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
+use App\Services\PackageManager;
use Illuminate\Support\Facades\File;
use Tests\Concerns\MocksGuzzleClient;
use Illuminate\Support\Facades\Storage;
@@ -22,7 +22,6 @@ class UpdateControllerTest extends TestCase
protected function setUp(): void
{
parent::setUp();
-
$this->actAs('superAdmin');
}
@@ -83,151 +82,31 @@ class UpdateControllerTest extends TestCase
$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')
- ->with('root')
- ->once()
- ->andReturnSelf();
- Storage::shouldReceive('makeDirectory')
- ->with('storage/update_cache')
- ->once()
- ->andReturn(false);
- $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'));
+ // Download
$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->assertTrue(Cache::has('tmp_path'));
- $this->assertFalse(Cache::has('download-progress'));
-
- // Start downloading
- Cache::flush();
- $this->withNewVersionAvailable()
- ->getJson('/admin/update/download?action=start-download')
- ->assertJson([
- 'errno' => 1,
- 'msg' => 'No temp path available, please try again.',
- ]);
-
- // Can't download update package
- $this->appendToGuzzleQueue([
- new Response(200, [], $this->generateFakeUpdateInfo('8.9.3')),
- new RequestException('Connection Error', new Request('GET', 'whatever')),
- ]);
- Cache::put('tmp_path', storage_path('update_cache/update.zip'));
- $this->getJson('/admin/update/download?action=start-download');
-
- // Download update package
- $fakeUpdatePackage = $this->generateFakeUpdateFile();
- $this->appendToGuzzleQueue([
- new Response(200, [], $this->generateFakeUpdateInfo('8.9.3')),
- new Response(200, [], fopen($fakeUpdatePackage, 'r')),
- ]);
- Cache::put('tmp_path', storage_path('update_cache/update.zip'));
- $this->getJson('/admin/update/download?action=start-download')
- ->assertJson([
- 'tmp_path' => storage_path('update_cache/update.zip'),
- ]);
- $this->assertFileExists(storage_path('update_cache/update.zip'));
-
- // No download progress available
- Cache::flush();
- $this->withNewVersionAvailable()
- ->getJson('/admin/update/download?action=get-progress')
- ->assertJson([]);
+ app()->instance(PackageManager::class, new Concerns\FakePackageManager(null, true));
+ $this->getJson('/admin/update/download?action=download')
+ ->assertJson(['errno' => 1]);
+ app()->bind(PackageManager::class, Concerns\FakePackageManager::class);
+ $this->getJson('/admin/update/download?action=download')
+ ->assertJson(['errno' => 0, 'msg' => trans('admin.update.complete')]);
// Get download progress
- Cache::put('download-progress', ['total' => 514, 'downloaded' => 114]);
- $this->withNewVersionAvailable()
- ->getJson('/admin/update/download?action=get-progress')
- ->assertJson([
- 'total' => 514,
- 'downloaded' => 114,
- ]);
-
- // No such zip archive
- Cache::put('tmp_path', storage_path('update_cache/nope.zip'));
- $this->withNewVersionAvailable()
- ->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');
- Cache::put('tmp_path', storage_path('update_cache/update.zip'));
- $this->withNewVersionAvailable()
- ->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'));
- File::shouldReceive('copyDirectory')
- ->with(storage_path('update_cache/8.9.3/vendor'), base_path('vendor'))
- ->andThrow(new Exception);
- File::shouldReceive('deleteDirectory')
- ->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/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->withNewVersionAvailable()
- ->getJson('/admin/update/download?action=extract')
- ->assertJson([
- 'errno' => 1,
- 'msg' => trans('admin.update.errors.overwrite'),
- ]);
+ $this->getJson('/admin/update/download?action=progress')
+ ->assertSee('0');
// Invalid action
- $this->withNewVersionAvailable()
- ->getJson('/admin/update/download?action=no')
+ $this->appendToGuzzleQueue(200, [], $this->generateFakeUpdateInfo('8.9.3'));
+ $this->getJson('/admin/update/download?action=no')
->assertJson([
'errno' => 1,
'msg' => trans('general.illegal-parameters'),
]);
}
- protected function withNewVersionAvailable()
- {
- $this->appendToGuzzleQueue(200, [], $this->generateFakeUpdateInfo('8.9.3'));
-
- return $this;
- }
-
protected function generateFakeUpdateInfo($version, $preview = false, $time = null)
{
$time = $time ?: time();
@@ -247,20 +126,4 @@ class UpdateControllerTest extends TestCase
],
]);
}
-
- 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;
- }
}