mirror of
https://code.castopod.org/adaures/castopod
synced 2025-06-03 16:02:02 +00:00
test(plugins): add test cases for loading manifest data
This commit is contained in:
parent
014facd5a1
commit
e2a90def88
@ -33,7 +33,11 @@
|
||||
},
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["plugins/**/manifest.json"],
|
||||
"fileMatch": [
|
||||
"plugins/**/manifest.json",
|
||||
"tests/modules/Plugins/Mocks/manifests/*.json",
|
||||
"tests/modules/Plugins/Mocks/plugins/**/manifest.json"
|
||||
],
|
||||
"url": "/workspaces/castopod/modules/Plugins/Manifest/manifest.schema.json"
|
||||
}
|
||||
]
|
||||
|
@ -32,11 +32,6 @@ abstract class BasePlugin implements PluginInterface
|
||||
|
||||
protected string $iconSrc;
|
||||
|
||||
/**
|
||||
* @var array<string,string>
|
||||
*/
|
||||
protected array $errors = [];
|
||||
|
||||
protected PluginStatus $status;
|
||||
|
||||
protected Manifest $manifest;
|
||||
@ -52,29 +47,11 @@ abstract class BasePlugin implements PluginInterface
|
||||
|
||||
// TODO: cache manifest data
|
||||
$manifestPath = $directory . '/manifest.json';
|
||||
$manifestContents = @file_get_contents($manifestPath);
|
||||
|
||||
if (! $manifestContents) {
|
||||
$manifestContents = '{}';
|
||||
$this->errors['manifest'] = lang('Plugins.errors.manifestMissing', [
|
||||
'manifestPath' => $manifestPath,
|
||||
]);
|
||||
}
|
||||
$this->manifest = new Manifest($this->key);
|
||||
$this->manifest->loadFromFile($manifestPath);
|
||||
|
||||
/** @var array<mixed>|null $manifestData */
|
||||
$manifestData = json_decode($manifestContents, true);
|
||||
|
||||
if ($manifestData === null) {
|
||||
$manifestData = [];
|
||||
$this->errors['manifest'] = lang('Plugins.errors.manifestJsonInvalid', [
|
||||
'manifestPath' => $manifestPath,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->manifest = new Manifest($this->key, $manifestData);
|
||||
$this->errors = [...$this->errors, ...Manifest::getPluginErrors($this->key)];
|
||||
|
||||
if ($this->errors !== []) {
|
||||
if ($this->getManifestErrors() !== []) {
|
||||
$this->status = PluginStatus::INVALID;
|
||||
} else {
|
||||
$this->status = get_plugin_option($this->key, 'active') ? PluginStatus::ACTIVE : PluginStatus::INACTIVE;
|
||||
@ -121,9 +98,9 @@ abstract class BasePlugin implements PluginInterface
|
||||
/**
|
||||
* @return array<string,string>
|
||||
*/
|
||||
final public function getErrors(): array
|
||||
final public function getManifestErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
return Manifest::getPluginErrors($this->key);
|
||||
}
|
||||
|
||||
final public function isHookDeclared(string $name): bool
|
||||
|
@ -26,7 +26,7 @@ class Manifest extends ManifestObject
|
||||
* @var array<string,string>
|
||||
*/
|
||||
protected const VALIDATION_RULES = [
|
||||
'name' => 'required|max_length[128]',
|
||||
'name' => 'required|max_length[128]|regex_match[/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*$/]',
|
||||
'version' => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]',
|
||||
'description' => 'permit_empty|max_length[256]',
|
||||
'authors' => 'permit_empty|is_list',
|
||||
|
@ -21,16 +21,10 @@ abstract class ManifestObject
|
||||
*/
|
||||
protected static array $errors = [];
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
public function __construct(
|
||||
protected readonly string $pluginKey,
|
||||
private readonly array $data,
|
||||
) {
|
||||
self::$errors[$pluginKey] = [];
|
||||
|
||||
$this->load();
|
||||
}
|
||||
|
||||
public function __get(string $name): mixed
|
||||
@ -47,14 +41,41 @@ abstract class ManifestObject
|
||||
return property_exists($this, $property);
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
public function loadFromFile(string $manifestPath): void
|
||||
{
|
||||
$manifestContents = @file_get_contents($manifestPath);
|
||||
|
||||
if (! $manifestContents) {
|
||||
$manifestContents = '{}';
|
||||
$this->addError('manifest', lang('Plugins.errors.manifestMissing', [
|
||||
'manifestPath' => $manifestPath,
|
||||
]));
|
||||
}
|
||||
|
||||
/** @var array<mixed>|null $manifestData */
|
||||
$manifestData = json_decode($manifestContents, true);
|
||||
|
||||
if ($manifestData === null) {
|
||||
$manifestData = [];
|
||||
$this->addError('manifest', lang('Plugins.errors.manifestJsonInvalid', [
|
||||
'manifestPath' => $manifestPath,
|
||||
]));
|
||||
}
|
||||
|
||||
$this->loadData($manifestData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
public function loadData(array $data): void
|
||||
{
|
||||
/** @var Validation $validation */
|
||||
$validation = service('validation');
|
||||
|
||||
$validation->setRules($this::VALIDATION_RULES);
|
||||
|
||||
if (! $validation->run($this->data)) {
|
||||
if (! $validation->run($data)) {
|
||||
foreach ($validation->getErrors() as $key => $message) {
|
||||
$this->addError($key, $message);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class Person extends ManifestObject
|
||||
|
||||
protected ?URI $url = null;
|
||||
|
||||
public function __construct(string $pluginKey, array|string $data)
|
||||
public function loadData(array|string $data): void
|
||||
{
|
||||
if (is_string($data)) {
|
||||
$result = preg_match(self::AUTHOR_STRING_PATTERN, $data, $matches);
|
||||
@ -51,6 +51,6 @@ class Person extends ManifestObject
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($pluginKey, $data);
|
||||
parent::loadData($data);
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
namespace Tests\Support\Database\Seeds;
|
||||
|
||||
use App\Database\Seeds\AppSeeder;
|
||||
use App\Database\Seeds\DevSeeder;
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class FakeSinglePodcastApiSeeder extends Seeder
|
@ -2,13 +2,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\Api\Rest\V1;
|
||||
namespace Tests\Modules\Api\Rest\V1;
|
||||
|
||||
use App\Database\Seeds\FakeSinglePodcastApiSeeder;
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\DatabaseTestTrait;
|
||||
use CodeIgniter\Test\FeatureTestTrait;
|
||||
use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
|
||||
|
||||
class EpisodeTest extends CIUnitTestCase
|
||||
{
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace modules\Api\Rest\V1;
|
||||
namespace Tests\Modules\Api\Rest\V1;
|
||||
|
||||
use App\Database\Seeds\FakeSinglePodcastApiSeeder;
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\DatabaseTestTrait;
|
||||
use CodeIgniter\Test\FeatureTestTrait;
|
||||
use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
|
||||
|
||||
class PodcastTest extends CIUnitTestCase
|
||||
{
|
||||
|
67
tests/modules/Plugins/ManifestTest.php
Normal file
67
tests/modules/Plugins/ManifestTest.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Modules\Api\Rest\V1;
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Modules\Plugins\Manifest\Manifest;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ManifestTest extends CIUnitTestCase
|
||||
{
|
||||
public function testLoadRequiredData(): void
|
||||
{
|
||||
$manifest = new Manifest('acme/hello-world');
|
||||
|
||||
// properties have not been set yet
|
||||
$this->assertNotEquals($manifest->name, 'acme/hello-world');
|
||||
$this->assertNotEquals($manifest->version, '1.0.0');
|
||||
|
||||
$manifest->loadFromFile(TESTPATH . 'modules/Plugins/Mocks/manifests/manifest-required.json');
|
||||
|
||||
// no errors
|
||||
$this->assertEmpty($manifest->getPluginErrors('acme/hello-world'));
|
||||
|
||||
// properties have been set
|
||||
$this->assertEquals($manifest->name, 'acme/hello-world');
|
||||
$this->assertEquals($manifest->version, '1.0.0');
|
||||
}
|
||||
|
||||
public function testLoadEmptyData(): void
|
||||
{
|
||||
$manifest = new Manifest('acme/hello-world');
|
||||
|
||||
$manifest->loadFromFile(TESTPATH . 'modules/Plugins/Mocks/manifests/manifest-empty.json');
|
||||
|
||||
$errors = $manifest->getPluginErrors('acme/hello-world');
|
||||
|
||||
$this->assertCount(2, $errors);
|
||||
|
||||
// missing required name and version
|
||||
$this->assertArrayHasKey('name', $errors);
|
||||
$this->assertArrayHasKey('version', $errors);
|
||||
}
|
||||
|
||||
public function testLoadValidData(): void
|
||||
{
|
||||
$manifest = new Manifest('acme/hello-world');
|
||||
|
||||
$manifest->loadFromFile(TESTPATH . 'modules/Plugins/Mocks/manifests/manifest-full-valid.json');
|
||||
|
||||
// no errors
|
||||
$this->assertEmpty($manifest->getPluginErrors('acme/hello-world'));
|
||||
}
|
||||
|
||||
public function testLoadInvalidData(): void
|
||||
{
|
||||
$manifest = new Manifest('acme/hello-world');
|
||||
|
||||
$manifest->loadFromFile(TESTPATH . 'modules/Plugins/Mocks/manifests/manifest-full-invalid.json');
|
||||
|
||||
// errors
|
||||
$this->assertNotEmpty($manifest->getPluginErrors('acme/hello-world'));
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{}
|
142
tests/modules/Plugins/Mocks/manifests/manifest-full-invalid.json
Normal file
142
tests/modules/Plugins/Mocks/manifests/manifest-full-invalid.json
Normal file
@ -0,0 +1,142 @@
|
||||
{
|
||||
"name": "acme/hello-world",
|
||||
"description": true,
|
||||
"version": "1.0.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Acme Corporation",
|
||||
"email": "acme@example.com",
|
||||
"url": "https://example.com/"
|
||||
}
|
||||
],
|
||||
"homepage": "https://example.com/",
|
||||
"license": ["MIT", "AGPLv3"],
|
||||
"keywords": ["seo", "analytics"],
|
||||
"hooks": ["rssAfterChannel"],
|
||||
"settings": {
|
||||
"general": [
|
||||
{
|
||||
"type": "radio-group",
|
||||
"key": "name",
|
||||
"label": "Name",
|
||||
"options": [
|
||||
{ "label": "Foo", "value": "foo", "hint": "This is a hint." },
|
||||
{ "label": "Bar", "value": "bar" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "email",
|
||||
"key": "email",
|
||||
"label": "Email"
|
||||
},
|
||||
{
|
||||
"type": "url",
|
||||
"key": "url",
|
||||
"label": "Your website URL"
|
||||
},
|
||||
{
|
||||
"type": "toggler",
|
||||
"key": "toggler",
|
||||
"label": "Toggle this?"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "number",
|
||||
"label": "Number"
|
||||
},
|
||||
{
|
||||
"type": "datetime",
|
||||
"key": "datetime",
|
||||
"label": "Enter a date",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"key": "select",
|
||||
"label": "Select something",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "select-multiple",
|
||||
"key": "select-multiple",
|
||||
"label": "Select multiple things",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "radio-group",
|
||||
"key": "radio-group",
|
||||
"label": "Radio Group",
|
||||
"helper": "This is a helper.",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "textarea",
|
||||
"key": "texting",
|
||||
"label": "Your text",
|
||||
"hint": "This is a hint."
|
||||
},
|
||||
{
|
||||
"type": "markdown",
|
||||
"key": "hello",
|
||||
"label": "Name Podcast",
|
||||
"hint": "This is a hint.",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"podcast": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name Podcast",
|
||||
"hint": "This is a hint."
|
||||
}
|
||||
],
|
||||
"episode": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name Episode",
|
||||
"helper": "This is a helper."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
143
tests/modules/Plugins/Mocks/manifests/manifest-full-valid.json
Normal file
143
tests/modules/Plugins/Mocks/manifests/manifest-full-valid.json
Normal file
@ -0,0 +1,143 @@
|
||||
{
|
||||
"name": "acme/hello-world",
|
||||
"description": "A Castopod plugin to add a hello world greeting to your RSS feed!",
|
||||
"version": "1.0.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Acme Corporation",
|
||||
"email": "acme@example.com",
|
||||
"url": "https://example.com/"
|
||||
}
|
||||
],
|
||||
"homepage": "https://example.com/",
|
||||
"license": "MIT",
|
||||
"keywords": ["seo", "analytics"],
|
||||
"hooks": ["rssAfterChannel"],
|
||||
"settings": {
|
||||
"general": [
|
||||
{
|
||||
"type": "radio-group",
|
||||
"key": "name",
|
||||
"label": "Name",
|
||||
"options": [
|
||||
{ "label": "Foo", "value": "foo", "hint": "This is a hint." },
|
||||
{ "label": "Bar", "value": "bar" },
|
||||
{ "label": "Baz", "value": "baz" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "email",
|
||||
"key": "email",
|
||||
"label": "Email"
|
||||
},
|
||||
{
|
||||
"type": "url",
|
||||
"key": "url",
|
||||
"label": "Your website URL"
|
||||
},
|
||||
{
|
||||
"type": "toggler",
|
||||
"key": "toggler",
|
||||
"label": "Toggle this?"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "number",
|
||||
"label": "Number"
|
||||
},
|
||||
{
|
||||
"type": "datetime",
|
||||
"key": "datetime",
|
||||
"label": "Enter a date",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"key": "select",
|
||||
"label": "Select something",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "select-multiple",
|
||||
"key": "select-multiple",
|
||||
"label": "Select multiple things",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "radio-group",
|
||||
"key": "radio-group",
|
||||
"label": "Radio Group",
|
||||
"helper": "This is a helper.",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "textarea",
|
||||
"key": "texting",
|
||||
"label": "Your text",
|
||||
"hint": "This is a hint."
|
||||
},
|
||||
{
|
||||
"type": "markdown",
|
||||
"key": "hello",
|
||||
"label": "Name Podcast",
|
||||
"hint": "This is a hint.",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"podcast": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name Podcast",
|
||||
"hint": "This is a hint."
|
||||
}
|
||||
],
|
||||
"episode": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name Episode",
|
||||
"helper": "This is a helper."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "acme/hello-world",
|
||||
"version": "1.0.0"
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Modules\Plugins\Mocks\Plugins\AcmeHelloUniverse;
|
||||
|
||||
use Modules\Plugins\Core\BasePlugin;
|
||||
|
||||
class Plugin extends BasePlugin
|
||||
{
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "acme/hello-universe",
|
||||
"version": "1.0.0"
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Modules\Plugins\Mocks\Plugins\AcmeHelloWorld;
|
||||
|
||||
use Modules\Plugins\Core\BasePlugin;
|
||||
|
||||
class Plugin extends BasePlugin
|
||||
{
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "acme/hello-world",
|
||||
"version": "1.0.0"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user