Add plugins management page
This commit is contained in:
parent
32104da31f
commit
f6d022c377
|
|
@ -79,29 +79,21 @@ class PluginController extends Controller
|
|||
|
||||
public function getPluginData(PluginManager $plugins)
|
||||
{
|
||||
$installed = $plugins->getPlugins();
|
||||
|
||||
return Datatables::of($installed)
|
||||
->setRowId('plugin-{{ $name }}')
|
||||
->editColumn('title', function ($plugin) {
|
||||
return trans($plugin->title ?: 'EMPTY');
|
||||
return $plugins->getPlugins()
|
||||
->map(function ($plugin) {
|
||||
return [
|
||||
'name' => $plugin->name,
|
||||
'title' => trans($plugin->title ?: 'EMPTY'),
|
||||
'author' => $plugin->author,
|
||||
'description' => trans($plugin->description ?: 'EMPTY'),
|
||||
'version' => $plugin->version,
|
||||
'url' => $plugin->url,
|
||||
'enabled' => $plugin->isEnabled(),
|
||||
'config' => $plugin->hasConfigView(),
|
||||
'dependencies' => $this->getPluginDependencies($plugin),
|
||||
];
|
||||
})
|
||||
->editColumn('description', function ($plugin) {
|
||||
return trans($plugin->description ?: 'EMPTY');
|
||||
})
|
||||
->editColumn('author', function ($plugin) {
|
||||
return ['author' => trans($plugin->author ?: 'EMPTY'), 'url' => $plugin->url];
|
||||
})
|
||||
->addColumn('dependencies', function ($plugin) {
|
||||
return $this->getPluginDependencies($plugin);
|
||||
})
|
||||
->addColumn('status', function ($plugin) {
|
||||
return trans('admin.plugins.status.'.($plugin->isEnabled() ? 'enabled' : 'disabled'));
|
||||
})
|
||||
->addColumn('operations', function ($plugin) {
|
||||
return ['enabled' => $plugin->isEnabled(), 'hasConfigView' => $plugin->hasConfigView()];
|
||||
})
|
||||
->make(true);
|
||||
->values();
|
||||
}
|
||||
|
||||
protected function getPluginDependencies(Plugin $plugin)
|
||||
|
|
|
|||
235
resources/assets/src/components/admin/Plugins.vue
Normal file
235
resources/assets/src/components/admin/Plugins.vue
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
<template>
|
||||
<section class="content">
|
||||
<vue-good-table
|
||||
:rows="plugins"
|
||||
:columns="columns"
|
||||
:search-options="tableOptions.search"
|
||||
:pagination-options="tableOptions.pagination"
|
||||
styleClass="vgt-table striped"
|
||||
:rowStyleClass="rowStyleClassFn"
|
||||
>
|
||||
<template slot="table-row" slot-scope="props">
|
||||
<span v-if="props.column.field === 'title'">
|
||||
<strong>{{ props.formattedRow[props.column.field] }}</strong>
|
||||
<div v-if="props.row.enabled" class="actions">
|
||||
<template v-if="props.row.config">
|
||||
<a
|
||||
class="text-primary"
|
||||
:href="`${baseUrl}/admin/plugins/config/${props.row.name}`"
|
||||
>{{ $t('admin.configurePlugin') }}</a> |
|
||||
</template>
|
||||
<a
|
||||
href="#"
|
||||
v-t="'admin.disablePlugin'"
|
||||
class="text-primary"
|
||||
@click="disablePlugin(props.row)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="actions">
|
||||
<a
|
||||
href="#"
|
||||
v-t="'admin.enablePlugin'"
|
||||
class="text-primary"
|
||||
@click="enablePlugin(props.row)"
|
||||
/> |
|
||||
<a
|
||||
href="#"
|
||||
v-t="'admin.deletePlugin'"
|
||||
class="text-danger"
|
||||
@click="deletePlugin(props.row)"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else-if="props.column.field === 'description'">
|
||||
<div><p>{{ props.formattedRow.description }}</p></div>
|
||||
<div class="plugin-version-author">
|
||||
{{ $t('admin.pluginVersion') }}
|
||||
<span class="text-primary">{{ props.row.version }}</span> |
|
||||
{{ $t('admin.pluginAuthor') }}
|
||||
<a :href="props.row.url">{{ props.row.author }}</a> |
|
||||
{{ $t('admin.pluginName') }}
|
||||
{{ props.row.name }}
|
||||
</div>
|
||||
</span>
|
||||
<span v-else-if="props.column.field === 'dependencies'">
|
||||
<span
|
||||
v-if="props.row.dependencies.requirements.length === 0"
|
||||
><i v-t="'admin.noDependencies'"></i></span>
|
||||
<div v-else>
|
||||
<span
|
||||
v-for="(semver, dep) in props.row.dependencies.requirements"
|
||||
:key="dep"
|
||||
class="label"
|
||||
:class="`bg-${dep in props.row.dependencies.unsatisfiedRequirements ? 'red' : 'green'}`"
|
||||
>
|
||||
{{ dep }}: {{ semver }}
|
||||
<br>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else v-text="props.formattedRow[props.column.field]" />
|
||||
</template>
|
||||
</vue-good-table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VueGoodTable } from 'vue-good-table';
|
||||
import 'vue-good-table/dist/vue-good-table.min.css';
|
||||
import toastr from 'toastr';
|
||||
import { swal } from '../../js/notify';
|
||||
|
||||
export default {
|
||||
name: 'Plugins',
|
||||
components: {
|
||||
VueGoodTable
|
||||
},
|
||||
props: {
|
||||
baseUrl: {
|
||||
default: blessing.base_url
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
plugins: [],
|
||||
columns: [
|
||||
{ field: 'title', label: this.$t('admin.pluginTitle'), width: '17%' },
|
||||
{
|
||||
field: 'description',
|
||||
label: this.$t('admin.pluginDescription'),
|
||||
sortable: false,
|
||||
width: '65%'
|
||||
},
|
||||
{
|
||||
field: 'dependencies',
|
||||
label: this.$t('admin.pluginDependencies'),
|
||||
sortable: false,
|
||||
globalSearchDisabled: true
|
||||
},
|
||||
],
|
||||
tableOptions: {
|
||||
search: {
|
||||
enabled: true,
|
||||
placeholder: this.$t('vendor.datatable.search')
|
||||
},
|
||||
pagination: {
|
||||
enabled: true,
|
||||
nextLabel: this.$t('vendor.datatable.next'),
|
||||
prevLabel: this.$t('vendor.datatable.prev'),
|
||||
rowsPerPageLabel: this.$t('vendor.datatable.rowsPerPage'),
|
||||
allLabel: this.$t('vendor.datatable.all'),
|
||||
ofLabel: this.$t('vendor.datatable.of')
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
this.fetchData();
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.plugins = await this.$http.get('/admin/plugins/data');
|
||||
},
|
||||
rowStyleClassFn(row) {
|
||||
return row.enabled ? 'plugin-enabled' : 'plugin';
|
||||
},
|
||||
async enablePlugin({ name, dependencies: { requirements }, originalIndex }) {
|
||||
if (requirements.length === 0) {
|
||||
const { dismiss } = await swal({
|
||||
text: this.$t('admin.noDependenciesNotice'),
|
||||
type: 'warning',
|
||||
showCancelButton: true
|
||||
});
|
||||
if (dismiss) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { errno, msg, reason } = await this.$http.post(
|
||||
'/admin/plugins/manage',
|
||||
{ action: 'enable', name }
|
||||
);
|
||||
if (errno === 0) {
|
||||
toastr.success(msg);
|
||||
this.$set(this.plugins[originalIndex], 'enabled', true);
|
||||
} else {
|
||||
const div = document.createElement('div');
|
||||
const p = document.createElement('p');
|
||||
p.textContent = msg;
|
||||
div.appendChild(p);
|
||||
const ul = document.createElement('ul');
|
||||
reason.forEach(item => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = item;
|
||||
ul.appendChild(li);
|
||||
});
|
||||
div.appendChild(ul);
|
||||
swal({
|
||||
type: 'warning',
|
||||
html: div.innerHTML.replace(/`([\w-_]+)`/g, '<code>$1</code>')
|
||||
});
|
||||
}
|
||||
},
|
||||
async disablePlugin({ name, originalIndex }) {
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/admin/plugins/manage',
|
||||
{ action: 'disable', name }
|
||||
);
|
||||
if (errno === 0) {
|
||||
toastr.success(msg);
|
||||
this.plugins[originalIndex].enabled = false;
|
||||
} else {
|
||||
swal({ type: 'warning', text: msg });
|
||||
}
|
||||
},
|
||||
async deletePlugin({ name }) {
|
||||
const { dismiss } = await swal({
|
||||
text: this.$t('admin.confirmDeletion'),
|
||||
type: 'warning',
|
||||
showCancelButton: true
|
||||
});
|
||||
if (dismiss) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/admin/plugins/manage',
|
||||
{ action: 'delete', name }
|
||||
);
|
||||
if (errno === 0) {
|
||||
toastr.success(msg);
|
||||
this.plugins = this.plugins.filter(plugin => plugin.name !== name);
|
||||
} else {
|
||||
swal({ type: 'warning', text: msg });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.actions {
|
||||
margin-top: 5px;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.plugin-version-author {
|
||||
color: #777;
|
||||
font-size: small;
|
||||
a {
|
||||
color: #337ab7;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin > td:first-child {
|
||||
border-left: 5px solid transparent;
|
||||
}
|
||||
|
||||
.plugin-enabled {
|
||||
background-color: #f7fcfe;
|
||||
}
|
||||
|
||||
.plugin-enabled > td:first-child {
|
||||
border-left: 5px solid #3c8dbc;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -34,6 +34,11 @@ export default [
|
|||
component: () => import('./admin/Customization'),
|
||||
el: '#change-color'
|
||||
},
|
||||
{
|
||||
path: 'admin/plugins/manage',
|
||||
component: () => import('./admin/Plugins'),
|
||||
el: '.content'
|
||||
},
|
||||
{
|
||||
path: 'admin/plugins/market',
|
||||
component: () => import('./admin/Market'),
|
||||
|
|
|
|||
138
resources/assets/tests/components/admin/Plugins.test.js
Normal file
138
resources/assets/tests/components/admin/Plugins.test.js
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import Vue from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import Plugins from '@/components/admin/Plugins';
|
||||
import { flushPromises } from '../../utils';
|
||||
import { swal } from '@/js/notify';
|
||||
import toastr from 'toastr';
|
||||
|
||||
jest.mock('@/js/notify');
|
||||
|
||||
test('render dependencies', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ name: 'a', dependencies: { requirements: [] } },
|
||||
{ name: 'b', dependencies: {
|
||||
requirements: { 'a': '^1.0.0', 'c': '^2.0.0' }, unsatisfiedRequirements: { 'c': {} }
|
||||
} }
|
||||
]);
|
||||
const wrapper = mount(Plugins);
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.text()).toContain('admin.noDependencies');
|
||||
expect(wrapper.find('span.label.bg-green').text()).toBe('a: ^1.0.0');
|
||||
expect(wrapper.find('span.label.bg-red').text()).toBe('c: ^2.0.0');
|
||||
});
|
||||
|
||||
test('render operation buttons', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ name: 'a', dependencies: { requirements: [] }, enabled: true, config: true },
|
||||
{ name: 'b', dependencies: { requirements: [] }, enabled: true, config: false },
|
||||
{ name: 'c', dependencies: { requirements: [] }, enabled: false },
|
||||
]);
|
||||
const wrapper = mount(Plugins);
|
||||
await flushPromises();
|
||||
const tbody = wrapper.find('tbody');
|
||||
|
||||
expect(tbody.find('tr:nth-child(1)').text()).toContain('admin.disablePlugin');
|
||||
expect(tbody.find('tr:nth-child(1)').text()).toContain('admin.configurePlugin');
|
||||
expect(tbody.find('tr:nth-child(2)').text()).not.toContain('admin.configurePlugin');
|
||||
expect(tbody.find('tr:nth-child(3)').text()).toContain('admin.enablePlugin');
|
||||
expect(tbody.find('tr:nth-child(3)').text()).toContain('admin.deletePlugin');
|
||||
});
|
||||
|
||||
test('enable plugin', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ name: 'a', dependencies: { requirements: [] }, enabled: false },
|
||||
{ name: 'b', dependencies: { requirements: {} }, enabled: false },
|
||||
]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1, msg: '1', reason: ['`a<div></div>`b'] })
|
||||
.mockResolvedValue({ errno: 0, msg: '0' });
|
||||
swal.mockResolvedValueOnce({ dismiss: 1 })
|
||||
.mockResolvedValueOnce({});
|
||||
const wrapper = mount(Plugins);
|
||||
await flushPromises();
|
||||
|
||||
wrapper.findAll('.actions').at(0).find('a').trigger('click');
|
||||
await flushPromises();
|
||||
expect(swal).toBeCalledWith({
|
||||
text: 'admin.noDependenciesNotice',
|
||||
type: 'warning',
|
||||
showCancelButton: true
|
||||
});
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
|
||||
wrapper.findAll('.actions').at(0).find('a').trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/admin/plugins/manage',
|
||||
{ action: 'enable', name: 'a' }
|
||||
);
|
||||
expect(swal).toBeCalledWith({
|
||||
type: 'warning',
|
||||
html: '<p>1</p><ul><li>`a<div></div>`b</li></ul>'
|
||||
});
|
||||
|
||||
wrapper.findAll('.actions').at(1).find('a').trigger('click');
|
||||
await flushPromises();
|
||||
expect(wrapper.text()).toContain('admin.disablePlugin');
|
||||
});
|
||||
|
||||
test('disable plugin', async () => {
|
||||
jest.spyOn(toastr, 'success');
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ name: 'a', dependencies: { requirements: [] }, enabled: true, config: false }
|
||||
]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1, msg: '1' })
|
||||
.mockResolvedValue({ errno: 0, msg: '0' });
|
||||
const wrapper = mount(Plugins);
|
||||
await flushPromises();
|
||||
const button = wrapper.find('.actions').find('a');
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/admin/plugins/manage',
|
||||
{ action: 'disable', name: 'a' }
|
||||
);
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(toastr.success).toBeCalledWith('0');
|
||||
});
|
||||
|
||||
test('delete plugin', async () => {
|
||||
jest.spyOn(toastr, 'success');
|
||||
Vue.prototype.$http.get.mockResolvedValue([
|
||||
{ name: 'a', dependencies: { requirements: [] }, enabled: false },
|
||||
]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1, msg: '1' })
|
||||
.mockResolvedValue({ errno: 0, msg: '0' });
|
||||
swal.mockResolvedValueOnce({ dismiss: 1 })
|
||||
.mockResolvedValue({});
|
||||
const wrapper = mount(Plugins);
|
||||
await flushPromises();
|
||||
const button = wrapper.find('.actions').findAll('a').at(1);
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(swal).toBeCalledWith({
|
||||
text: 'admin.confirmDeletion',
|
||||
type: 'warning',
|
||||
showCancelButton: true
|
||||
});
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/admin/plugins/manage',
|
||||
{ action: 'delete', name: 'a' }
|
||||
);
|
||||
expect(swal).toBeCalledWith({ type: 'warning', text: '1' });
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(toastr.success).toBeCalledWith('0');
|
||||
});
|
||||
|
|
@ -260,9 +260,9 @@ admin:
|
|||
noSuchUser: 没有这个用户哦~
|
||||
changePlayerNameNotice: 请输入新的角色名:
|
||||
emptyPlayerName: 您还没填写角色名呢
|
||||
configurePlugin: 插件配置
|
||||
configurePlugin: 配置
|
||||
noPluginConfigNotice: 插件已被禁用或无配置页
|
||||
deletePlugin: 删除插件
|
||||
deletePlugin: 删除
|
||||
noDependencies: 无要求
|
||||
whyDependencies: 为什么会这样?
|
||||
statusEnabled: 已启用
|
||||
|
|
@ -278,8 +278,8 @@ admin:
|
|||
updatePlugin: 更新
|
||||
pluginUpdating: 正在更新...
|
||||
confirmUpdate: 确定将「:plugin」从 :old 升级至 :new?
|
||||
enablePlugin: 启用插件
|
||||
disablePlugin: 禁用插件
|
||||
enablePlugin: 启用
|
||||
disablePlugin: 禁用
|
||||
confirmDeletion: 真的要删除这个插件吗?
|
||||
noDependenciesNotice: >-
|
||||
此插件没有声明任何依赖关系,这代表它有可能并不兼容此版本的 Blessing
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
@section('title', trans('general.plugin-manage'))
|
||||
|
||||
@section('style')
|
||||
<style> .btn { margin-right: 4px; } </style>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<!-- Content Wrapper. Contains page content -->
|
||||
|
|
@ -18,33 +14,7 @@
|
|||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
|
||||
@if (session()->has('message'))
|
||||
<div class="callout callout-success" role="alert">
|
||||
{{ session('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="box">
|
||||
<div class="box-body table-bordered">
|
||||
<table id="plugin-table" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>@lang('admin.plugins.name')</th>
|
||||
<th>@lang('admin.plugins.description')</th>
|
||||
<th>@lang('admin.plugins.author')</th>
|
||||
<th>@lang('admin.plugins.version')</th>
|
||||
<th>@lang('admin.plugins.dependencies')</th>
|
||||
<th>@lang('admin.plugins.status.title')</th>
|
||||
<th>@lang('admin.plugins.operations.title')</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section><!-- /.content -->
|
||||
<section class="content"></section><!-- /.content -->
|
||||
</div><!-- /.content-wrapper -->
|
||||
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ namespace Tests;
|
|||
|
||||
use App\Events;
|
||||
use ZipArchive;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Tests\Concerns\GeneratesFakePlugins;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
|
@ -11,39 +13,16 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|||
class PluginControllerTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
use GeneratesFakePlugins;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$plugins = [
|
||||
'example-plugin' => 'example-plugin_v1.0.0.zip',
|
||||
'avatar-api' => 'avatar-api_v1.1.1.zip'
|
||||
];
|
||||
$this->generateFakePlugin(['name' => 'fake-plugin-for-test', 'version' => '1.1.4']);
|
||||
$this->generateFakePlugin(['name' => 'fake-plugin-with-config-view', 'version' => '5.1.4', 'config' => 'config.blade.php']);
|
||||
|
||||
foreach ($plugins as $plugin_name => $filename) {
|
||||
if (! file_exists(base_path('plugins/'.$plugin_name))) {
|
||||
$user_agent = menv('USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36');
|
||||
|
||||
$context = stream_context_create(['http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "User-Agent: $user_agent"
|
||||
]]);
|
||||
|
||||
file_put_contents(
|
||||
storage_path('testing/'.$filename),
|
||||
file_get_contents("https://coding.net/u/printempw/p/bs-plugins-archive/git/raw/master/$filename", false, $context)
|
||||
);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$zip->open(storage_path('testing/'.$filename));
|
||||
$zip->extractTo(base_path('plugins/'));
|
||||
$zip->close();
|
||||
unlink(storage_path('testing/'.$filename));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->actAs('admin');
|
||||
return $this->actAs('superAdmin');
|
||||
}
|
||||
|
||||
public function testShowManage()
|
||||
|
|
@ -55,13 +34,14 @@ class PluginControllerTest extends TestCase
|
|||
public function testConfig()
|
||||
{
|
||||
// Plugin is disabled
|
||||
$this->get('/admin/plugins/config/example-plugin')
|
||||
->assertStatus(404);
|
||||
$this->get('/admin/plugins/config/fake-plugin-with-config-view')
|
||||
->assertNotFound();
|
||||
|
||||
// Plugin is enabled but it doesn't have config view
|
||||
plugin('avatar-api')->setEnabled(true);
|
||||
plugin('fake-plugin-for-test')->setEnabled(true);
|
||||
$this->get('/admin/plugins/config/avatar-api')
|
||||
->assertStatus(404);
|
||||
->assertSee(trans('admin.plugins.operations.no-config-notice'))
|
||||
->assertNotFound();
|
||||
|
||||
// Plugin has config view
|
||||
plugin('example-plugin')->setEnabled(true);
|
||||
|
|
@ -79,38 +59,28 @@ class PluginControllerTest extends TestCase
|
|||
]);
|
||||
|
||||
// Invalid action
|
||||
$this->postJson('/admin/plugins/manage', ['name' => 'avatar-api'])
|
||||
$this->postJson('/admin/plugins/manage', ['name' => 'fake-plugin-for-test'])
|
||||
->assertJson([
|
||||
'errno' => 1,
|
||||
'msg' => trans('admin.invalid-action')
|
||||
]);
|
||||
|
||||
// Retrieve requirements
|
||||
$this->postJson('/admin/plugins/manage', [
|
||||
'name' => 'avatar-api',
|
||||
'action' => 'requirements'
|
||||
])->assertJson([
|
||||
'isRequirementsSatisfied' => true,
|
||||
'requirements' => [],
|
||||
'unsatisfiedRequirements' => []
|
||||
]);
|
||||
|
||||
// Enable a plugin with unsatisfied dependencies
|
||||
app('plugins')->getPlugin('avatar-api')->setRequirements([
|
||||
app('plugins')->getPlugin('fake-plugin-for-test')->setRequirements([
|
||||
'blessing-skin-server' => '^3.4.0',
|
||||
'example-plugin' => '^6.6.6',
|
||||
'fake-plugin-with-config-view' => '^6.6.6',
|
||||
'whatever' => '^1.0.0'
|
||||
]);
|
||||
app('plugins')->enable('example-plugin');
|
||||
app('plugins')->enable('fake-plugin-with-config-view');
|
||||
$this->postJson('/admin/plugins/manage', [
|
||||
'name' => 'avatar-api',
|
||||
'name' => 'fake-plugin-for-test',
|
||||
'action' => 'enable'
|
||||
])->assertJson([
|
||||
'errno' => 1,
|
||||
'msg' => trans('admin.plugins.operations.unsatisfied.notice'),
|
||||
'reason' => [
|
||||
trans('admin.plugins.operations.unsatisfied.version', [
|
||||
'name' => 'example-plugin',
|
||||
'name' => 'fake-plugin-with-config-view',
|
||||
'constraint' => '^6.6.6'
|
||||
]),
|
||||
trans('admin.plugins.operations.unsatisfied.disabled', [
|
||||
|
|
@ -120,60 +90,68 @@ class PluginControllerTest extends TestCase
|
|||
]);
|
||||
|
||||
// Enable a plugin
|
||||
app('plugins')->getPlugin('avatar-api')->setRequirements([]);
|
||||
app('plugins')->getPlugin('fake-plugin-for-test')->setRequirements([]);
|
||||
$this->expectsEvents(Events\PluginWasEnabled::class);
|
||||
$this->postJson('/admin/plugins/manage', [
|
||||
'name' => 'avatar-api',
|
||||
'name' => 'fake-plugin-for-test',
|
||||
'action' => 'enable'
|
||||
])->assertJson([
|
||||
'errno' => 0,
|
||||
'msg' => trans(
|
||||
'admin.plugins.operations.enabled',
|
||||
['plugin' => plugin('avatar-api')->title]
|
||||
['plugin' => plugin('fake-plugin-for-test')->title]
|
||||
)
|
||||
]);
|
||||
|
||||
// Disable a plugin
|
||||
$this->postJson('/admin/plugins/manage', [
|
||||
'name' => 'avatar-api',
|
||||
'name' => 'fake-plugin-for-test',
|
||||
'action' => 'disable'
|
||||
])->assertJson([
|
||||
'errno' => 0,
|
||||
'msg' => trans(
|
||||
'admin.plugins.operations.disabled',
|
||||
['plugin' => plugin('avatar-api')->title]
|
||||
['plugin' => plugin('fake-plugin-for-test')->title]
|
||||
)
|
||||
]);
|
||||
$this->expectsEvents(Events\PluginWasDisabled::class);
|
||||
|
||||
// Delete a plugin
|
||||
$this->postJson('/admin/plugins/manage', [
|
||||
'name' => 'avatar-api',
|
||||
'name' => 'fake-plugin-for-test',
|
||||
'action' => 'delete'
|
||||
])->assertJson([
|
||||
'errno' => 0,
|
||||
'msg' => trans('admin.plugins.operations.deleted')
|
||||
]);
|
||||
$this->expectsEvents(Events\PluginWasDeleted::class);
|
||||
$this->assertFalse(file_exists(base_path('plugins/avatar-api/')));
|
||||
$this->assertFalse(file_exists(base_path('plugins/fake-plugin-for-test/')));
|
||||
}
|
||||
|
||||
public function testGetPluginData()
|
||||
{
|
||||
$this->getJson('/admin/plugins/data')
|
||||
->assertJsonStructure([
|
||||
'data' => [[
|
||||
[
|
||||
'name',
|
||||
'version',
|
||||
'path',
|
||||
'title',
|
||||
'description',
|
||||
'author',
|
||||
'url',
|
||||
'namespace',
|
||||
'enabled',
|
||||
'config',
|
||||
'dependencies'
|
||||
]]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
// Clean fake plugins
|
||||
File::deleteDirectory(base_path('plugins/fake-plugin-for-test'));
|
||||
File::deleteDirectory(base_path('plugins/fake-plugin-with-config-view'));
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user