feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run

- Base: wintercms/winter branch 1.2 (full framework)
- Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS
- Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication
- Partials: hero (offline-first), features, modes (offline/nube toggle),
  screenshots, pricing (3 planes), comparison, FAQ, CTA
- Plugin VivesPOS.Site with ContactForm
- Dockerfile: PHP 8.2 Apache, port 80, healthcheck
- Added winter/wn-pages, blog, sitemap, seo plugins
- Active theme set to vivespos
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

View File

@@ -0,0 +1,297 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\TestCase;
use Cms\Classes\Theme;
use System\Classes\CombineAssets;
class CombineAssetsTest extends TestCase
{
public function setUp() : void
{
parent::setUp();
CombineAssets::resetCache();
}
//
// Tests
//
public function testCombiner()
{
$combiner = CombineAssets::instance();
/*
* Supported file extensions should exist
*/
$jsExt = $cssExt = self::getProtectedProperty($combiner, 'jsExtensions');
$this->assertIsArray($jsExt);
$cssExt = self::getProtectedProperty($combiner, 'cssExtensions');
$this->assertIsArray($cssExt);
/*
* Check service methods
*/
$this->assertTrue(method_exists($combiner, 'combine'));
$this->assertTrue(method_exists($combiner, 'resetCache'));
}
public function testCombine()
{
$combiner = CombineAssets::instance();
$url = $combiner->combine(
[
'assets/css/style1.css',
'assets/css/style2.css'
],
base_path() . '/modules/system/tests/fixtures/themes/test'
);
$this->assertNotNull($url);
$this->assertRegExp('/\w+[-]\d+/i', $url); // Must contain hash-number
$url = $combiner->combine(
[
'assets/js/script1.js',
'assets/js/script2.js'
],
base_path() . '/modules/system/tests/fixtures/themes/test'
);
$this->assertNotNull($url);
$this->assertRegExp('/\w+[-]\d+/i', $url); // Must contain hash-number
}
public function testPutCache()
{
$sampleId = md5('testhash');
$sampleStore = ['version' => 12345678];
$samplePath = '/tests/fixtures/Cms/themes/test';
$combiner = CombineAssets::instance();
$value = self::callProtectedMethod($combiner, 'putCache', [$sampleId, $sampleStore]);
$this->assertTrue($value);
}
public function testGetTargetPath()
{
$combiner = CombineAssets::instance();
$value = self::callProtectedMethod($combiner, 'getTargetPath', ['/combine']);
$this->assertEquals('combine/', $value);
$value = self::callProtectedMethod($combiner, 'getTargetPath', ['/index.php/combine']);
$this->assertEquals('index-php/combine/', $value);
}
public function testMakeCacheId()
{
$sampleResources = ['assets/css/style1.css', 'assets/css/style2.css'];
$samplePath = base_path() . '/modules/system/tests/fixtures/cms/themes/test';
$combiner = CombineAssets::instance();
self::setProtectedProperty($combiner, 'localPath', $samplePath);
$value = self::callProtectedMethod($combiner, 'getCacheKey', [$sampleResources]);
$this->assertEquals(md5($samplePath.implode('|', $sampleResources)), $value);
}
public function testResetCache()
{
$combiner = CombineAssets::instance();
$this->assertNull($combiner->resetCache());
}
/**
* Regression for GHSA-58fp-mcx6-7qf9. A writable theme `.less` file containing
* `@import (inline) "<absolute-path>"` must not disclose server files.
*/
public function testLessCompilerBlocksAbsolutePathImport()
{
[$themeDir, $secretPath] = $this->setupLessLeakFixture(
'@import (inline) "%SECRET%"; .x { color: red; }'
);
try {
$css = $this->compileLessTo($themeDir, 'assets/less/poc.less');
$this->assertStringNotContainsString('APP_KEY', $css);
$this->assertStringNotContainsString('combine-leak-canary', $css);
} finally {
$this->teardownLessLeakFixture($themeDir, $secretPath);
}
}
/**
* Regression for the relative-traversal path of GHSA-58fp-mcx6-7qf9. less.php's
* auto-added `currentDirectory` import_dir entry resolves `..` traversal
* natively; without the key-collision override in LessImportResolver, a theme
* `.less` could still escape via `@import (inline) "../../../etc/passwd"`.
*/
public function testLessCompilerBlocksRelativeTraversalImport()
{
// From themeDir/assets/less/poc.less, traverse up enough to escape the
// theme tree, the themes root, and out to the secret file the fixture
// wrote at sys_get_temp_dir().
[$themeDir, $secretPath] = $this->setupLessLeakFixture(
'@import (inline) "' . str_repeat('../', 20) . ltrim($this->lastSecretPath, '/') . '"; .x { color: red; }'
);
try {
$css = $this->compileLessTo($themeDir, 'assets/less/poc.less');
$this->assertStringNotContainsString('APP_KEY', $css);
$this->assertStringNotContainsString('combine-leak-canary', $css);
} finally {
$this->teardownLessLeakFixture($themeDir, $secretPath);
}
}
/**
* Legitimate same-tree `@import "partial.less"` must still resolve through
* the gate, otherwise we've broken every theme that uses partials.
*/
public function testLessCompilerAllowsLegitimatePartial()
{
$themeDir = $this->makeTempThemeDir();
$mainPath = $themeDir . '/assets/less/main.less';
$partialPath = $themeDir . '/assets/less/partial.less';
file_put_contents($partialPath, '.partial-marker { color: orange; }');
file_put_contents($mainPath, '@import "partial.less"; .main-marker { color: blue; }');
try {
$css = $this->compileLessTo($themeDir, 'assets/less/main.less');
$this->assertStringContainsString('partial-marker', $css);
$this->assertStringContainsString('main-marker', $css);
} finally {
\File::deleteDirectory($themeDir);
}
}
/**
* Regression for GHSA-2223-f22x-24cq. A writable theme `.js` asset containing
* `=include ../../../.env` must not disclose server files through the combiner,
* whose output is served unauthenticated via the `combine/{file}` route.
*/
public function testJavascriptImporterBlocksTraversalImport()
{
$themeDir = $this->makeTempThemeDir();
// A real `.js` secret outside the theme subtree (but under base_path so
// Assetic's FileAsset root check passes). It escapes via `..` traversal but
// lands outside every allowed import root, so it must not be inlined.
$secretPath = dirname($themeDir) . '/js-secret-' . bin2hex(random_bytes(4)) . '.js';
file_put_contents($secretPath, 'var LEAK = "combine-leak-canary";');
file_put_contents(
$themeDir . '/assets/poc.js',
"/*\n=include ../../" . basename($secretPath) . "\n*/\n"
);
try {
$js = $this->compileJsTo($themeDir, 'assets/poc.js');
$this->assertStringNotContainsString('combine-leak-canary', $js);
} finally {
@unlink($secretPath);
\File::deleteDirectory($themeDir);
}
}
/**
* The `.js`-only extension gate must block disclosure of non-JS files (e.g.
* `.env`) before any path resolution, even inside an otherwise reachable tree.
*/
public function testJavascriptImporterBlocksDisallowedExtension()
{
$themeDir = $this->makeTempThemeDir();
$secretPath = dirname($themeDir) . '/js-secret-' . bin2hex(random_bytes(4)) . '.env';
file_put_contents($secretPath, "APP_KEY=combine-leak-canary\n");
file_put_contents(
$themeDir . '/assets/poc.js',
"/*\n=include ../../" . basename($secretPath) . "\n*/\n"
);
try {
$js = $this->compileJsTo($themeDir, 'assets/poc.js');
$this->assertStringNotContainsString('combine-leak-canary', $js);
} finally {
@unlink($secretPath);
\File::deleteDirectory($themeDir);
}
}
/**
* Legitimate same-tree `=include partial.js` must still resolve, otherwise the
* hardening would break every asset that composes its own bundle.
*/
public function testJavascriptImporterAllowsSameTreeInclude()
{
$themeDir = $this->makeTempThemeDir();
file_put_contents($themeDir . '/assets/partial.js', 'var PARTIAL = "partial-marker";');
file_put_contents($themeDir . '/assets/main.js', "/*\n=include partial.js\n*/\nvar MAIN = 1;");
try {
$js = $this->compileJsTo($themeDir, 'assets/main.js');
$this->assertStringContainsString('partial-marker', $js);
} finally {
\File::deleteDirectory($themeDir);
}
}
protected function compileJsTo(string $themeDir, string $relativeAsset): string
{
$dest = sys_get_temp_dir() . '/winter-combine-out-' . bin2hex(random_bytes(4)) . '.js';
try {
CombineAssets::instance()->combineToFile([$relativeAsset], $dest, $themeDir);
return file_get_contents($dest) ?: '';
} finally {
@unlink($dest);
}
}
/** @var string */
protected $lastSecretPath = '';
/**
* @return array{0:string,1:string} [theme dir, secret path]
*/
protected function setupLessLeakFixture(string $pocTemplate): array
{
$themeDir = $this->makeTempThemeDir();
$secretPath = tempnam(sys_get_temp_dir(), 'combine-leak-canary-');
file_put_contents($secretPath, "APP_KEY=do-not-leak-via-combiner\n");
$this->lastSecretPath = $secretPath;
$poc = str_replace('%SECRET%', $secretPath, $pocTemplate);
file_put_contents($themeDir . '/assets/less/poc.less', $poc);
return [$themeDir, $secretPath];
}
protected function teardownLessLeakFixture(string $themeDir, string $secretPath): void
{
@unlink($secretPath);
\File::deleteDirectory($themeDir);
}
protected function makeTempThemeDir(): string
{
// Must live under base_path() because Assetic's FileAsset enforces that
// the source be within the configured root, which CombineAssets sets to
// public_path() (equal to base_path() in this install). Using sys_get_temp_dir()
// would trigger "source is not in the root directory" errors.
$themeDir = base_path('storage/framework/cache/security-tests/theme-' . bin2hex(random_bytes(4)));
mkdir($themeDir . '/assets/less', 0777, true);
return $themeDir;
}
protected function compileLessTo(string $themeDir, string $relativeAsset): string
{
$dest = sys_get_temp_dir() . '/winter-combine-out-' . bin2hex(random_bytes(4)) . '.css';
try {
CombineAssets::instance()->combineToFile([$relativeAsset], $dest, $themeDir);
return file_get_contents($dest) ?: '';
} finally {
@unlink($dest);
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\TestCase;
use System\Classes\PluginManager;
use Validator;
class CoreLangTest extends TestCase
{
public function testValidationTranslator()
{
$translator = $this->app['translator'];
$translator->setLocale('en');
$validator = Validator::make(
['name' => 'me'],
['name' => 'required|min:5']
);
$this->assertTrue($validator->fails());
$messages = $validator->messages();
$this->assertCount(1, $messages);
$this->assertEquals('The name must be at least 5 characters.', $messages->all()[0]);
}
public function testValidCoreLanguageFiles()
{
$translator = $this->app['translator'];
$locales = $translator->get('system::lang.locale');
$this->assertNotEmpty($locales);
$locales = array_keys($locales);
$modules = ['system', 'backend', 'cms'];
$files = ['lang.php', 'validation.php', 'client.php'];
foreach ($modules as $module) {
foreach ($locales as $locale) {
foreach ($files as $file) {
$srcPath = base_path() . '/modules/'.$module.'/lang/'.$locale.'/'.$file;
if (!file_exists($srcPath)) {
continue;
}
$messages = require $srcPath;
$this->assertNotEmpty($messages);
$this->assertNotCount(0, $messages);
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace System\Tests\Classes;
use ReflectionClass;
use System\Tests\Bootstrap\TestCase;
use Winter\Storm\Exception\ApplicationException;
use System\Classes\FileManifest;
class FileManifestTest extends TestCase
{
/** @var FileManifest instance */
protected $fileManifest;
/** @var root path */
protected $root;
public function setUp(): void
{
parent::setUp();
$this->root = base_path('modules/system/tests/fixtures/manifest/1_0_1');
$this->fileManifest = new FileManifest($this->root, ['test', 'test2']);
}
public function testGetFiles()
{
$this->assertEquals([
'/modules/test/file1.php' => '6f9b0b94528a85b2a6bb67b5621e074aef1b4c9fc9ee3ea1bd69100ea14cb3db',
'/modules/test/file2.php' => '96ae9f6b6377ad29226ea169f952de49fc29ae895f18a2caed76aeabdf050f1b',
'/modules/test2/file1.php' => '94bd47b1ac7b2837b31883ebcd38c8101687321f497c3c4b9744f68ae846721d',
], $this->fileManifest->getFiles());
}
public function testGetModuleChecksums()
{
$this->assertEquals([
'test' => 'c0b794ff210862a4ce16223802efe6e28969f5a4fb42480ec8c2fef2da23d181',
'test2' => '32c9f2fb6e0a22dde288a0fe1e4834798360b25e5a91d2597409d9302221381d',
], $this->fileManifest->getModuleChecksums());
}
public function testGetFilesInvalidRoot()
{
$this->expectException(ApplicationException::class);
$this->expectExceptionMessage('Invalid root specified for the file manifest.');
$this->fileManifest->setRoot(base_path('tests/fixtures/manifest/invalid'));
$this->fileManifest->getFiles();
}
public function testSingleModule()
{
$this->fileManifest->setModules(['test']);
$this->assertEquals([
'/modules/test/file1.php' => '6f9b0b94528a85b2a6bb67b5621e074aef1b4c9fc9ee3ea1bd69100ea14cb3db',
'/modules/test/file2.php' => '96ae9f6b6377ad29226ea169f952de49fc29ae895f18a2caed76aeabdf050f1b',
], $this->fileManifest->getFiles());
$this->assertEquals([
'test' => 'c0b794ff210862a4ce16223802efe6e28969f5a4fb42480ec8c2fef2da23d181',
], $this->fileManifest->getModuleChecksums());
}
public function testGetFilename()
{
$class = new ReflectionClass('System\Classes\FileManifest');
$method = $class->getMethod('getFilename');
$method->setAccessible(true);
$filename = '/modules/test/file1.php';
$this->assertEquals($filename, $method->invoke($this->fileManifest, $this->root . $filename));
}
}

View File

@@ -0,0 +1,482 @@
<?php
namespace System\Tests\Classes;
use Backend\Facades\Backend;
use Cms\Classes\Controller as CmsController;
use Cms\Classes\Theme;
use Config;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Event;
use Storage;
use System\Classes\ImageResizer;
use System\Classes\MediaLibrary;
use System\Models\File as FileModel;
use System\Tests\Bootstrap\PluginTestCase;
use URL;
use Winter\Storm\Exception\SystemException;
class ImageResizerTest extends PluginTestCase
{
use ArraySubsetAsserts;
protected $originalThemesPath = '';
public function setUp(): void
{
parent::setUp();
$this->originalThemesPath = Config::get('cms.themesPath');
Config::set('cms.themesPath', '/modules/system/tests/fixtures/themes');
Config::set('cms.activeTheme', 'test');
Event::flush('cms.theme.getActiveTheme');
Theme::resetCache();
}
public function tearDown(): void
{
$this->removeMedia();
Config::set('cms.themesPath', $this->originalThemesPath);
ImageResizer::flushAvailableSources();
parent::tearDown();
}
/**
* Tests configuration through the constructor as well as events.
*
* @return void
*/
public function testConfiguration()
{
if (!in_array('Cms', Config::get('cms.loadModules', []))) {
$this->markTestSkipped('The CMS module is not active.');
}
// Resize with default options
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
100,
100
);
self::assertArraySubset([
'width' => 100,
'height' => 100,
'options' => [
'mode' => 'auto',
'offset' => [0, 0],
'sharpen' => 0,
'interlace' => false,
'quality' => 90,
'extension' => 'png',
],
], $imageResizer->getConfig());
// Resize with customised options
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
150,
120,
[
'mode' => 'fit',
'offset' => [2, 2],
'sharpen' => 23,
'interlace' => true,
'quality' => 73,
'extension' => 'jpg'
]
);
self::assertArraySubset([
'width' => 150,
'height' => 120,
'options' => [
'mode' => 'fit',
'offset' => [2, 2],
'sharpen' => 23,
'interlace' => true,
'quality' => 73,
'extension' => 'jpg'
],
], $imageResizer->getConfig());
// Resize with an customised defaults
Event::listen('system.resizer.getDefaultOptions', function (&$options) {
$options = array_merge($options, [
'mode' => 'fit',
'offset' => [2, 2],
'sharpen' => 23,
'interlace' => true,
'quality' => 73,
]);
});
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
100,
100,
[]
);
self::assertArraySubset([
'width' => 100,
'height' => 100,
'options' => [
'mode' => 'fit',
'offset' => [2, 2],
'sharpen' => 23,
'interlace' => true,
'quality' => 73,
'extension' => 'png',
],
], $imageResizer->getConfig());
Event::forget('system.resizer.getDefaultOptions');
// Resize with a falsey height specified
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
100,
false
);
self::assertArraySubset([
'width' => 100,
'height' => 0,
], $imageResizer->getConfig());
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
100,
null
);
self::assertArraySubset([
'width' => 100,
'height' => 0,
], $imageResizer->getConfig());
// Resize with a falsey width specified
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
'',
100
);
self::assertArraySubset([
'width' => 0,
'height' => 100,
], $imageResizer->getConfig());
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
"0",
100
);
self::assertArraySubset([
'width' => 0,
'height' => 100,
], $imageResizer->getConfig());
}
/**
* Tests URLs for sources that can be accessed via URL.
*
* @return void
*/
public function testURLSources()
{
if (!in_array('Cms', Config::get('cms.loadModules', []))) {
$this->markTestSkipped('The CMS module is not active.');
}
// Theme URL (absolute URL)
$this->setUpStorage();
$this->copyMedia();
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Theme URL (relative URL)
$this->setUpStorage();
$this->copyMedia();
$imageResizer = new ImageResizer(
'/modules/system/tests/fixtures/themes/test/assets/images/winter.png',
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Media URL (absolute URL)
$this->setUpStorage();
$this->copyMedia();
$imageResizer = new ImageResizer(
URL::to(MediaLibrary::url('winter.png')),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Media URL (relative URL)
$this->setUpStorage();
$this->copyMedia();
$imageResizer = new ImageResizer(
MediaLibrary::url('winter.png'),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Media URL (absolute URL)
$this->setUpStorage();
$this->copyMedia();
$imageResizer = new ImageResizer(
URL::to(MediaLibrary::url('winter.png')),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Plugin URL (relative URL)
$imageResizer = new ImageResizer(
'/modules/system/tests/fixtures/plugins/database/tester/assets/images/avatar.png',
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Plugin URL (absolute URL)
$imageResizer = new ImageResizer(
URL::to('modules/system/tests/fixtures/plugins/database/tester/assets/images/avatar.png'),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Module URL (relative URL)
$imageResizer = new ImageResizer(
'/modules/backend/assets/images/favicon.png',
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Module URL (absolute URL)
$imageResizer = new ImageResizer(
Backend::skinAsset('assets/images/favicon.png'),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// URL for a FileModel instance (absolute URL)
$fileModel = new FileModel();
$fileModel->fromFile(base_path('modules/system/tests/fixtures/plugins/database/tester/assets/images/avatar.png'));
$fileModel->save();
$imageResizer = new ImageResizer(
FileModel::first()->getPath(),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Remove FileModel instance
$fileModel->delete();
// URL of a FileModel instance (relative URL)
$fileModel = new FileModel();
$fileModel->fromFile(base_path('modules/system/tests/fixtures/plugins/database/tester/assets/images/avatar.png'));
$fileModel->save();
$imageResizer = new ImageResizer(
str_replace(url('') . '/', '/', FileModel::first()->getPath()),
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
}
public function testDirectSources()
{
// FileModel instance itself
$fileModel = new FileModel();
$fileModel->fromFile(base_path('modules/system/tests/fixtures/plugins/database/tester/assets/images/avatar.png'));
$fileModel->save();
$imageResizer = new ImageResizer(
$fileModel,
100,
100
);
$this->assertEquals('png', $imageResizer->getConfig()['options']['extension']);
// Remove FileModel instance
$fileModel->delete();
}
public function testInvalidInputPath()
{
$this->expectException(SystemException::class);
$this->expectExceptionMessageMatches('/^Unable to process the provided image/');
$imageResizer = new ImageResizer(
'/plugins/database/tester/assets/images/MISSING.png',
100,
100
);
}
public function testInvalidInputFileModel()
{
$this->expectException(SystemException::class);
$this->expectExceptionMessageMatches('/^Unable to process the provided image/');
$imageResizer = new ImageResizer(
FileModel::first(),
100,
100
);
}
public function testSpaceInFilename()
{
// Media URL with space
$this->setUpStorage();
$this->copyMedia();
$imageResizer = new ImageResizer(
URL::to(MediaLibrary::url('winter space.png')),
100,
100
);
$this->assertStringContainsString('winter%20space', $imageResizer->getResizedUrl(), 'Resized URLs are not properly URL encoded');
}
public function testGetResizedUrl()
{
if (!in_array('Cms', Config::get('cms.loadModules', []))) {
$this->markTestSkipped('The CMS module is not active.');
}
$imageResizer = new ImageResizer((new CmsController())->themeUrl('assets/images/winter.png'));
Config::set('cms.linkPolicy', 'force');
$url = $imageResizer->getResizedUrl();
$this->assertTrue(starts_with($url, 'http'));
Config::set('cms.linkPolicy', 'detect');
$url = $imageResizer->getResizedUrl();
$this->assertTrue(starts_with($url, Config::get('cms.storage.resized.path', '/storage/tests/app/resized')));
}
public function testGetResizerUrl()
{
if (!in_array('Cms', Config::get('cms.loadModules', []))) {
$this->markTestSkipped('The CMS module is not active.');
}
$imageResizer = new ImageResizer((new CmsController())->themeUrl('assets/images/winter.png'));
Config::set('cms.linkPolicy', 'force');
$url = $imageResizer->getResizerUrl();
$this->assertTrue(starts_with($url, 'http'));
Config::set('cms.linkPolicy', 'detect');
$url = $imageResizer->getResizerUrl();
$this->assertTrue(starts_with($url, '/resizer/'));
// test dots' double-encoding
// @see https://github.com/wintercms/winter/pull/1493
$this->assertTrue(ends_with($url, '%252Epng'));
// Verify the encoded URL round-trips through the resizer route's decoding and
// signature verification. A fresh instance is required as the identifier is
// cached on first generation and the link policy has changed since then. The
// router decodes the parameter once before it reaches getValidResizedUrl().
$imageResizer = new ImageResizer((new CmsController())->themeUrl('assets/images/winter.png'));
[$identifier, $encodedUrl] = array_slice(explode('/', $imageResizer->getResizerUrl()), 2);
$this->assertSame(
$imageResizer->getResizedUrl(),
ImageResizer::getValidResizedUrl($identifier, rawurldecode($encodedUrl))
);
}
public function testResizerRedirect()
{
if (!in_array('Cms', Config::get('cms.loadModules', []))) {
$this->markTestSkipped('The CMS module is not active.');
}
$this->setUpStorage();
$this->copyMedia();
Config::set('cms.storage.resized', [
'disk' => 'test_local',
'folder' => 'resized',
'path' => '/storage/temp/app/resized',
]);
$imageResizer = new ImageResizer((new CmsController())->themeUrl('assets/images/winter.png'), 50, 50);
// The resizer route responds with a permanent redirect as a resizer URL can
// only ever target the resized URL embedded and signed within it, and this
// also exercises the full round-trip of the double-encoded URL parameter
// through the actual router
$response = $this->get($imageResizer->getResizerUrl());
$response->assertStatus(301);
$response->assertRedirect($imageResizer->getResizedUrl());
// Clean up the generated image
Storage::disk('test_local')->deleteDirectory('resized');
}
protected function setUpStorage()
{
$this->app->useStoragePath(base_path('storage/temp'));
Config::set('filesystems.disks.test_local', [
'driver' => 'local',
'root' => storage_path('app'),
]);
Config::set('cms.storage.media', [
'disk' => 'test_local',
'folder' => 'media',
'path' => '/storage/temp/app/media',
]);
}
protected function copyMedia()
{
$mediaPath = storage_path('app/media');
if (!is_dir($mediaPath)) {
mkdir($mediaPath, 0777, true);
}
foreach (glob(base_path('modules/system/tests/fixtures/media/*')) as $file) {
$path = pathinfo($file);
copy($file, $mediaPath . DIRECTORY_SEPARATOR . $path['basename']);
}
}
protected function removeMedia()
{
if ($this->app->storagePath() !== base_path('storage/temp')) {
return;
}
foreach (glob(storage_path('app/media/*')) as $file) {
unlink($file);
}
rmdir(storage_path('app/media'));
rmdir(storage_path('app'));
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\TestCase;
use System\Classes\MarkupManager;
class MarkupManagerTest extends TestCase
{
public function setUp() : void
{
parent::setUp();
include_once base_path() . '/modules/system/tests/fixtures/plugins/winter/tester/Plugin.php';
}
//
// Tests
//
public function testIsWildCallable()
{
$manager = MarkupManager::instance();
/*
* Negatives
*/
$callable = 'something';
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
$this->assertFalse($result);
$callable = ['Form', 'open'];
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
$this->assertFalse($result);
$callable = function () {
return 'O, Hai!';
};
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
$this->assertFalse($result);
/*
* String
*/
$callable = 'something_*';
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
$this->assertTrue($result);
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'delicious']);
$this->assertEquals('something_delicious', $result);
/*
* Array
*/
$callable = ['Class', 'foo_*'];
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
$this->assertTrue($result);
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'bar']);
$this->assertArrayHasKey(0, $result);
$this->assertArrayHasKey(1, $result);
$this->assertEquals('Class', $result[0]);
$this->assertEquals('foo_bar', $result[1]);
$callable = ['My*', 'method'];
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
$this->assertTrue($result);
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'Class']);
$this->assertArrayHasKey(0, $result);
$this->assertArrayHasKey(1, $result);
$this->assertEquals('MyClass', $result[0]);
$this->assertEquals('method', $result[1]);
$callable = ['My*', 'my*'];
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
$this->assertTrue($result);
$result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'Food']);
$this->assertArrayHasKey(0, $result);
$this->assertArrayHasKey(1, $result);
$this->assertEquals('MyFood', $result[0]);
$this->assertEquals('myFood', $result[1]);
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\TestCase;
use Illuminate\Filesystem\FilesystemAdapter;
use System\Classes\MediaLibrary;
class MediaLibraryTest extends TestCase
{
public function setUp(): void
{
MediaLibrary::forgetInstance();
parent::setUp();
}
public function tearDown(): void
{
$this->removeMedia();
parent::tearDown();
}
public function invalidPathsProvider()
{
return [
['./file'],
['../secret'],
['.../secret'],
['/../secret'],
['/.../secret'],
['/secret/..'],
['file/../secret'],
['file/..'],
['......./secret'],
['./file'],
];
}
public function validPathsProvider()
{
return [
['file'],
['folder/file'],
['/file'],
['/folder/file'],
['/.file'],
['/..file'],
['/...file'],
['file.ext'],
['file..ext'],
['file...ext'],
['one,two.ext'],
['one(two)[].ext'],
['one=(two)[].ext'],
['one_(two)[].ext'],
/*
Example of a unicode-based filename with a single quote
@see: https://github.com/octobercms/october/pull/4564
*/
['BG中国通讯期刊(Blend\'r)创刊号.pdf'],
];
}
/**
* @dataProvider invalidPathsProvider
*/
public function testInvalidPathsOnValidatePath($path)
{
$this->expectException('ApplicationException');
MediaLibrary::validatePath($path);
}
/**
* @dataProvider validPathsProvider
*/
public function testValidPathsOnValidatePath($path)
{
$result = MediaLibrary::validatePath($path);
$this->assertIsString($result);
}
public function testListFolderContents()
{
$this->setUpStorage();
$this->copyMedia();
$contents = MediaLibrary::instance()->listFolderContents();
$this->assertNotEmpty($contents, 'Media library item is not discovered');
$this->assertCount(3, $contents);
$this->assertEquals('file', $contents[2]->type, 'Media library item does not have the right type');
$this->assertEquals('/winter.png', $contents[2]->path, 'Media library item does not have the right path');
$this->assertNotEmpty($contents[2]->lastModified, 'Media library item last modified is empty');
$this->assertNotEmpty($contents[2]->size, 'Media library item size is empty');
$this->assertEquals('file', $contents[0]->type, 'Media library item does not have the right type');
$this->assertEquals('/text.txt', $contents[0]->path, 'Media library item does not have the right path');
$this->assertNotEmpty($contents[0]->lastModified, 'Media library item last modified is empty');
$this->assertNotEmpty($contents[0]->size, 'Media library item size is empty');
}
public function testListAllDirectories()
{
$disk = $this->createConfiguredMock(FilesystemAdapter::class, [
'allDirectories' => [
'/media/.ignore1',
'/media/.ignore2',
'/media/dir',
'/media/dir/sub',
'/media/exclude',
'/media/hidden',
'/media/hidden/sub1',
'/media/hidden/sub1/deep1',
'/media/hidden/sub2',
'/media/hidden but not really',
'/media/name'
]
]);
$this->app['config']->set('cms.storage.media.folder', 'media');
$this->app['config']->set('cms.storage.media.ignore', ['hidden']);
$this->app['config']->set('cms.storage.media.ignorePatterns', ['^\..*']);
$instance = MediaLibrary::instance();
$this->setProtectedProperty($instance, 'storageDisk', $disk);
$this->assertEquals(['/', '/dir', '/dir/sub', '/hidden but not really', '/name'], $instance->listAllDirectories(['/exclude']));
}
protected function setUpStorage()
{
$this->app->useStoragePath(base_path('storage/temp'));
config(['filesystems.disks.test_local' => [
'driver' => 'local',
'root' => storage_path('app'),
]]);
config(['cms.storage.media' => [
'disk' => 'test_local',
'folder' => 'media',
'path' => '/storage/app/media',
]]);
}
protected function copyMedia()
{
$mediaPath = storage_path('app/media');
if (!is_dir($mediaPath)) {
mkdir($mediaPath, 0777, true);
}
foreach (glob(base_path('modules/system/tests/fixtures/media/*')) as $file) {
$path = pathinfo($file);
copy($file, $mediaPath . DIRECTORY_SEPARATOR . $path['basename']);
}
}
protected function removeMedia()
{
if ($this->app->storagePath() !== base_path('storage/temp')) {
return;
}
foreach (glob(storage_path('app/media/*')) as $file) {
unlink($file);
}
rmdir(storage_path('app/media'));
rmdir(storage_path('app'));
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\PluginManagerTestCase;
class PluginBaseTest extends PluginManagerTestCase
{
//
// Tests
//
public function testGetPluginVersions()
{
$expectedVersions = [
'1.0.1' => [
'Added some upgrade file and some "seeding"',
'some_seeding_file.php' //does not exist
],
'1.0.2' => [
'Added some stuff',
],
'1.0.3' => [
'Bug fix update that uses no scripts'
],
'1.0.4' => [
'Another fix'
],
'1.0.5' => [
'Create blog settings table',
'Another update message',
'Yet one more update message',
],
'1.1.0' => [
'!!! Drop support for blog settings',
],
'1.2.0' => [
'!!! Security update - see: https://wintercms.com',
],
'1.3.0' => [
'!!! We\'ve refactored major parts of this plugin. Please see the website for more information.',
],
'1.3.1' => [
'Minor bug fix Please see changelog',
],
'1.3.2' => [
'Added support for Translate plugin. Added some new languages.',
],
'1.4.1' => [
'!!! Major update here.',
],
'1.5.0' => [
'!!! Another major update to fix several issues',
],
'1.5.1' => [
'Improved signature with the Test::method()',
'Translation updates.',
],
];
$plugin = $this->manager->findByIdentifier('Winter.Tester');
$versions = $plugin->getPluginVersions(false);
$this->assertNotNull($versions);
$this->assertEquals($expectedVersions, $versions);
}
}

View File

@@ -0,0 +1,498 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\PluginManagerTestCase;
use System\Classes\PluginManager;
use System\Classes\PluginBase;
class PluginManagerTest extends PluginManagerTestCase
{
const INSTALLED_PLUGIN_COUNT = 17;
const ENABLED_PLUGIN_COUNT = 14;
const PLUGIN_NAMESPACE_COUNT = 18;
const PLUGIN_VENDOR_COUNT = 5;
//
// Tests
//
public function testLoadPlugins()
{
$result = $this->manager->loadPlugins();
$this->assertCount(static::INSTALLED_PLUGIN_COUNT, $result);
$this->assertArrayHasKey('Winter.NoUpdates', $result);
$this->assertArrayHasKey('Winter.Sample', $result);
$this->assertArrayHasKey('Winter.Tester', $result);
$this->assertArrayHasKey('Database.Tester', $result);
$this->assertArrayHasKey('TestVendor.Test', $result);
$this->assertArrayHasKey('DependencyTest.Found', $result);
$this->assertArrayHasKey('DependencyTest.NotFound', $result);
$this->assertArrayHasKey('DependencyTest.WrongCase', $result);
$this->assertArrayHasKey('DependencyTest.Dependency', $result);
$this->assertArrayNotHasKey('TestVendor.Goto', $result);
$this->assertInstanceOf('Winter\NoUpdates\Plugin', $result['Winter.NoUpdates']);
$this->assertInstanceOf('Winter\Sample\Plugin', $result['Winter.Sample']);
$this->assertInstanceOf('Winter\Tester\Plugin', $result['Winter.Tester']);
$this->assertInstanceOf('Database\Tester\Plugin', $result['Database.Tester']);
$this->assertInstanceOf('TestVendor\Test\Plugin', $result['TestVendor.Test']);
$this->assertInstanceOf('DependencyTest\Found\Plugin', $result['DependencyTest.Found']);
$this->assertInstanceOf('DependencyTest\NotFound\Plugin', $result['DependencyTest.NotFound']);
$this->assertInstanceOf('DependencyTest\WrongCase\Plugin', $result['DependencyTest.WrongCase']);
$this->assertInstanceOf('DependencyTest\Dependency\Plugin', $result['DependencyTest.Dependency']);
}
public function testUnloadablePlugin()
{
$pluginNamespaces = $this->manager->getPluginNamespaces();
$result = $this->manager->loadPlugin('\\testvendor\\goto', $pluginNamespaces['\\testvendor\\goto']);
$this->assertNull($result);
}
public function testGetPluginPath()
{
$result = $this->manager->getPluginPath('Winter\Tester');
$basePath = str_replace('\\', '/', base_path());
$this->assertEquals($basePath . '/modules/system/tests/fixtures/plugins/winter/tester', $result);
}
public function testGetPlugins()
{
$result = $this->manager->getPlugins();
$this->assertCount(static::ENABLED_PLUGIN_COUNT, $result);
$this->assertArrayHasKey('Winter.NoUpdates', $result);
$this->assertArrayHasKey('Winter.Sample', $result);
$this->assertArrayHasKey('Winter.Tester', $result);
$this->assertArrayHasKey('Database.Tester', $result);
$this->assertArrayHasKey('TestVendor.Test', $result);
$this->assertArrayHasKey('DependencyTest.Found', $result);
$this->assertArrayHasKey('DependencyTest.WrongCase', $result);
$this->assertArrayHasKey('DependencyTest.Dependency', $result);
$this->assertArrayNotHasKey('DependencyTest.NotFound', $result);
$this->assertArrayNotHasKey('TestVendor.Goto', $result);
$this->assertInstanceOf('Winter\NoUpdates\Plugin', $result['Winter.NoUpdates']);
$this->assertInstanceOf('Winter\Sample\Plugin', $result['Winter.Sample']);
$this->assertInstanceOf('Winter\Tester\Plugin', $result['Winter.Tester']);
$this->assertInstanceOf('Database\Tester\Plugin', $result['Database.Tester']);
$this->assertInstanceOf('TestVendor\Test\Plugin', $result['TestVendor.Test']);
$this->assertInstanceOf('DependencyTest\Found\Plugin', $result['DependencyTest.Found']);
$this->assertInstanceOf('DependencyTest\WrongCase\Plugin', $result['DependencyTest.WrongCase']);
$this->assertInstanceOf('DependencyTest\Dependency\Plugin', $result['DependencyTest.Dependency']);
}
public function testFindByNamespace()
{
$result = $this->manager->findByNamespace('Winter\Tester');
$this->assertInstanceOf('Winter\Tester\Plugin', $result);
}
public function testHasPlugin()
{
$result = $this->manager->hasPlugin('Winter\Tester');
$this->assertTrue($result);
$result = $this->manager->hasPlugin('DependencyTest.Found');
$this->assertTrue($result);
$result = $this->manager->hasPlugin('DependencyTest\WrongCase');
$this->assertTrue($result);
$result = $this->manager->hasPlugin('DependencyTest\NotFound');
$this->assertTrue($result);
$result = $this->manager->hasPlugin('Winter\XXXXX');
$this->assertFalse($result);
/**
* Test case for https://github.com/octobercms/october/pull/4337
*/
$result = $this->manager->hasPlugin('dependencyTest\Wrongcase');
$this->assertTrue($result);
$result = $this->manager->hasPlugin('dependencyTest.Wrongcase');
$this->assertTrue($result);
}
public function testGetPluginNamespaces()
{
$result = $this->manager->getPluginNamespaces();
$this->assertCount(static::PLUGIN_NAMESPACE_COUNT, $result);
$this->assertArrayHasKey('\winter\noupdates', $result);
$this->assertArrayHasKey('\winter\sample', $result);
$this->assertArrayHasKey('\winter\tester', $result);
$this->assertArrayHasKey('\database\tester', $result);
$this->assertArrayHasKey('\testvendor\test', $result);
$this->assertArrayHasKey('\testvendor\goto', $result);
$this->assertArrayHasKey('\dependencytest\found', $result);
$this->assertArrayHasKey('\dependencytest\notfound', $result);
$this->assertArrayHasKey('\dependencytest\wrongcase', $result);
$this->assertArrayHasKey('\dependencytest\dependency', $result);
}
public function testGetVendorAndPluginNames()
{
$vendors = $this->manager->getVendorAndPluginNames();
$this->assertCount(static::PLUGIN_VENDOR_COUNT, $vendors);
$this->assertArrayHasKey('winter', $vendors);
$this->assertArrayHasKey('noupdates', $vendors['winter']);
$this->assertArrayHasKey('sample', $vendors['winter']);
$this->assertArrayHasKey('tester', $vendors['winter']);
$this->assertArrayHasKey('database', $vendors);
$this->assertArrayHasKey('tester', $vendors['database']);
$this->assertArrayHasKey('testvendor', $vendors);
$this->assertArrayHasKey('test', $vendors['testvendor']);
$this->assertArrayHasKey('goto', $vendors['testvendor']);
$this->assertArrayHasKey('dependencytest', $vendors);
$this->assertArrayHasKey('found', $vendors['dependencytest']);
$this->assertArrayHasKey('notfound', $vendors['dependencytest']);
$this->assertArrayHasKey('wrongcase', $vendors['dependencytest']);
$this->assertArrayHasKey('dependency', $vendors['dependencytest']);
}
public function testPluginDetails()
{
$testPlugin = $this->manager->findByNamespace('Winter\XXXXX');
$this->assertNull($testPlugin);
$testPlugin = $this->manager->findByNamespace('Winter\Tester');
$this->assertNotNull($testPlugin);
$pluginDetails = $testPlugin->pluginDetails();
$this->assertEquals('Winter Test Plugin', $pluginDetails['name']);
$this->assertEquals('Test plugin used by unit tests.', $pluginDetails['description']);
$this->assertEquals('Alexey Bobkov, Samuel Georges', $pluginDetails['author']);
}
public function testUnregisterall()
{
$result = $this->manager->getPlugins();
$this->assertCount(static::ENABLED_PLUGIN_COUNT, $result);
$this->manager->unregisterAll();
$this->assertEmpty($this->manager->getPlugins());
}
public function testGetDependencies()
{
$result = $this->manager->getDependencies('DependencyTest.Found');
$this->assertCount(1, $result);
$this->assertContains('DependencyTest.Dependency', $result);
$result = $this->manager->getDependencies('DependencyTest.WrongCase');
$this->assertCount(1, $result);
$this->assertContains('Dependencytest.dependency', $result);
$result = $this->manager->getDependencies('DependencyTest.NotFound');
$this->assertCount(1, $result);
$this->assertContains('DependencyTest.Missing', $result);
}
public function testIsDisabled()
{
$result = $this->manager->isDisabled('DependencyTest.Found');
$this->assertFalse($result);
$result = $this->manager->isDisabled('DependencyTest.WrongCase');
$this->assertFalse($result);
$result = $this->manager->isDisabled('DependencyTest.NotFound');
$this->assertTrue($result);
/**
* Test case for https://github.com/octobercms/october/pull/4838
*/
$result = $this->manager->isDisabled('dependencyTest\Wrongcase');
$this->assertFalse($result);
$result = $this->manager->isDisabled('dependencyTest.Wrongcase');
$this->assertFalse($result);
$result = $this->manager->isDisabled('dependencytest.notfound');
$this->assertTrue($result);
}
public function testExists()
{
$result = $this->manager->exists('DependencyTest.Found');
$this->assertTrue($result);
$result = $this->manager->exists('DependencyTest.WrongCase');
$this->assertTrue($result);
$result = $this->manager->exists('DependencyTest.NotFound');
$this->assertFalse($result);
$result = $this->manager->exists('Unknown.Plugin');
$this->assertFalse($result);
}
public function testReplacement()
{
$this->assertFalse($this->manager->isDisabled('Winter.Replacement'));
$this->assertTrue($this->manager->isDisabled('Winter.Original'));
$this->assertTrue($this->manager->isDisabled('Winter.InvalidReplacement'));
$this->assertEquals('Winter.Replacement', $this->manager->findByIdentifier('Winter.Original')->getPluginIdentifier());
}
public function testHasPluginReplacement()
{
// check a replaced plugin
$this->assertTrue($this->manager->hasPlugin('Winter.Original'));
$this->assertTrue($this->manager->isDisabled('Winter.Original'));
// check a replacement plugin
$this->assertTrue($this->manager->hasPlugin('Winter.Replacement'));
$this->assertFalse($this->manager->isDisabled('Winter.Replacement'));
// check a plugin where the replacement is invalid
$this->assertTrue($this->manager->hasPlugin('Winter.InvalidReplacement'));
$this->assertTrue($this->manager->isDisabled('Winter.InvalidReplacement'));
// check a plugin replacing a plugin not found on disk
$this->assertTrue($this->manager->hasPlugin('Winter.ReplaceNotInstalled'));
$this->assertFalse($this->manager->isDisabled('Winter.ReplaceNotInstalled'));
// ensure searching for the alias of a replacement (plugin not installed)
$this->assertTrue($this->manager->hasPlugin('Winter.NotInstalled'));
$this->assertInstanceOf(\Winter\Replacement\Plugin::class, $this->manager->findByIdentifier('Winter.Original'));
$this->assertInstanceOf(\Winter\Replacement\Plugin::class, $this->manager->findByIdentifier('Winter.Replacement'));
// check getting a plugin via it's not installed original plugin identifier
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('Winter.NotInstalled'));
$this->assertNull($this->manager->findByIdentifier('Winter.NotInstalled', true));
// force getting the original plugin
$this->assertInstanceOf(\Winter\Original\Plugin::class, $this->manager->findByIdentifier('Winter.Original', true));
}
public function testHasPluginReplacementMixedCase()
{
// test checking casing of installed plugin (resolved via getNormalizedIdentifier())
$this->assertTrue($this->manager->hasPlugin('Winter.ReplaceNotInstalled'));
$this->assertTrue($this->manager->hasPlugin('Winter.replaceNotInstalled'));
$this->assertTrue($this->manager->hasPlugin('Winter.replacenotInstalled'));
$this->assertTrue($this->manager->hasPlugin('winter.replacenotInstalled'));
$this->assertTrue($this->manager->hasPlugin('winter.replacenotinstalled'));
// test checking casing of installed replaced plugin (resolved via getNormalizedIdentifier() & replacementMap)
$this->assertTrue($this->manager->hasPlugin('Winter.Original'));
$this->assertTrue($this->manager->hasPlugin('Winter.original'));
$this->assertTrue($this->manager->hasPlugin('winter.original'));
// test checking casing of uninstalled plugin (resolved via strtolower() on replacement keys)
$this->assertTrue($this->manager->hasPlugin('Winter.NotInstalled'));
$this->assertTrue($this->manager->hasPlugin('Winter.notInstalled'));
$this->assertTrue($this->manager->hasPlugin('winter.notInstalled'));
$this->assertTrue($this->manager->hasPlugin('Winter.notinstalled'));
}
public function testExistsReplacementMixedCase()
{
// test checking casing of installed plugin (resolved via getNormalizedIdentifier())
$this->assertTrue($this->manager->exists('Winter.ReplaceNotInstalled'));
$this->assertTrue($this->manager->exists('Winter.replaceNotInstalled'));
$this->assertTrue($this->manager->exists('Winter.replacenotInstalled'));
$this->assertTrue($this->manager->exists('winter.replacenotInstalled'));
$this->assertTrue($this->manager->exists('winter.replacenotinstalled'));
// test checking casing of installed replaced plugin (resolved via getNormalizedIdentifier() & replacementMap)
$this->assertFalse($this->manager->exists('Winter.Original'));
$this->assertFalse($this->manager->exists('Winter.original'));
$this->assertFalse($this->manager->exists('winter.original'));
// test checking casing of uninstalled plugin (resolved via strtolower() on replacement keys)
$this->assertTrue($this->manager->exists('Winter.NotInstalled'));
$this->assertTrue($this->manager->exists('Winter.notInstalled'));
$this->assertTrue($this->manager->exists('winter.notInstalled'));
$this->assertTrue($this->manager->exists('Winter.notinstalled'));
}
public function testFindByIdentifierReplacementMixedCase()
{
// test resolving plugin with mixed casing
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('Winter.ReplaceNotInstalled'));
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('Winter.replaceNotInstalled'));
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('Winter.replacenotInstalled'));
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('winter.replacenotInstalled'));
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('winter.replacenotinstalled'));
// test resolving replacement plugin with mixed casing
$this->assertInstanceOf(\Winter\Replacement\Plugin::class, $this->manager->findByIdentifier('Winter.Original'));
$this->assertInstanceOf(\Winter\Replacement\Plugin::class, $this->manager->findByIdentifier('Winter.original'));
$this->assertInstanceOf(\Winter\Replacement\Plugin::class, $this->manager->findByIdentifier('winter.original'));
// test resolving original plugin with mixed casing when ignoring replacements
$this->assertInstanceOf(\Winter\Original\Plugin::class, $this->manager->findByIdentifier('Winter.Original', true));
$this->assertInstanceOf(\Winter\Original\Plugin::class, $this->manager->findByIdentifier('Winter.original', true));
$this->assertInstanceOf(\Winter\Original\Plugin::class, $this->manager->findByIdentifier('winter.original', true));
// test resolving replacement plugin of uninstalled plugin with mixed casing
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('Winter.NotInstalled'));
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('Winter.notInstalled'));
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('winter.notInstalled'));
$this->assertInstanceOf(\Winter\ReplaceNotInstalled\Plugin::class, $this->manager->findByIdentifier('Winter.notinstalled'));
}
public function testGetReplacements()
{
$replacementPluginReplaces = $this->manager->findByIdentifier('Winter.Replacement')->getReplaces();
$this->assertIsArray($replacementPluginReplaces);
$this->assertCount(1, $replacementPluginReplaces);
$this->assertEquals('Winter.Original', $replacementPluginReplaces[0]);
$invalidPluginReplaces = $this->manager->findByIdentifier('Winter.InvalidReplacement')->getReplaces();
$this->assertIsArray($invalidPluginReplaces);
$this->assertCount(1, $invalidPluginReplaces);
$this->assertEquals('Winter.Tester', $invalidPluginReplaces[0]);
}
public function testReplaceVersion()
{
$invalidReplacementPlugin = $this->manager->findByIdentifier('Winter.InvalidReplacement');
$this->assertTrue($invalidReplacementPlugin->canReplacePlugin('Winter.Tester', '9'));
$this->assertTrue($invalidReplacementPlugin->canReplacePlugin('Winter.Tester', '9.0'));
$this->assertTrue($invalidReplacementPlugin->canReplacePlugin('Winter.Tester', '11.0.0'));
$this->assertFalse($invalidReplacementPlugin->canReplacePlugin('Winter.Tester', '8.0'));
$replacementPlugin = $this->manager->findByIdentifier('Winter.Replacement');
$this->assertTrue($replacementPlugin->canReplacePlugin('Winter.Original', '1.0.2'));
$this->assertTrue($replacementPlugin->canReplacePlugin('Winter.Original', '1.0'));
$this->assertFalse($replacementPlugin->canReplacePlugin('Winter.Original', '2.0.1'));
}
public function testActiveReplacementMap()
{
$map = $this->manager->getActiveReplacementMap();
$this->assertArrayHasKey('Winter.Original', $map);
$this->assertEquals('Winter.Replacement', $map['Winter.Original']);
$this->assertEquals('Winter.Replacement', $this->manager->getActiveReplacementMap('Winter.Original'));
$this->assertNull($this->manager->getActiveReplacementMap('Winter.InvalidReplacement'));
}
public function testFlagDisableStatus()
{
$plugin = $this->manager->findByIdentifier('DependencyTest.Dependency');
$flags = $this->manager->getPluginFlags($plugin);
$this->assertEmpty($flags);
$plugin = $this->manager->findByIdentifier('DependencyTest.NotFound');
$flags = $this->manager->getPluginFlags($plugin);
$this->assertCount(1, $flags);
$this->assertArrayHasKey(PluginManager::DISABLED_MISSING_DEPENDENCIES, $flags);
$plugin = $this->manager->findByIdentifier('Winter.InvalidReplacement');
$flags = $this->manager->getPluginFlags($plugin);
$this->assertCount(1, $flags);
$this->assertArrayHasKey(PluginManager::DISABLED_REPLACEMENT_FAILED, $flags);
$plugin = $this->manager->findByIdentifier('Winter.Original', true);
$flags = $this->manager->getPluginFlags($plugin);
$this->assertCount(1, $flags);
$this->assertArrayHasKey(PluginManager::DISABLED_REPLACED, $flags);
}
public function testFlagDisabling()
{
$plugin = $this->manager->findByIdentifier('Winter.Tester', true);
$flags = $this->manager->getPluginFlags($plugin);
$this->assertEmpty($flags);
$this->manager->disablePlugin($plugin);
$flags = $this->manager->getPluginFlags($plugin);
$this->assertCount(1, $flags);
$this->assertArrayHasKey(PluginManager::DISABLED_BY_USER, $flags);
$this->manager->enablePlugin($plugin);
$flags = $this->manager->getPluginFlags($plugin);
$this->assertEmpty($flags);
$this->manager->disablePlugin($plugin, PluginManager::DISABLED_BY_CONFIG);
$flags = $this->manager->getPluginFlags($plugin);
$this->assertCount(1, $flags);
$this->assertArrayHasKey(PluginManager::DISABLED_BY_CONFIG, $flags);
$this->manager->enablePlugin($plugin, PluginManager::DISABLED_BY_CONFIG);
$flags = $this->manager->getPluginFlags($plugin);
$this->assertEmpty($flags);
}
public function testPluginNormalization()
{
// test lower to upper
$this->assertEquals('Database.Tester', $this->manager->normalizeIdentifier('database.tester'));
$this->assertEquals('Database.Tester', $this->manager->getNormalizedIdentifier('database.tester'));
// test exact match
$this->assertEquals('DependencyTest.Found', $this->manager->normalizeIdentifier('DependencyTest.Found'));
$this->assertEquals('DependencyTest.Found', $this->manager->getNormalizedIdentifier('DependencyTest.Found'));
// test mixed case
$this->assertEquals('DependencyTest.Found', $this->manager->normalizeIdentifier('Dependencytest.Found'));
$this->assertEquals('DependencyTest.Found', $this->manager->getNormalizedIdentifier('Dependencytest.Found'));
// test typeo
$this->assertEquals('dpendencytest.Found', $this->manager->normalizeIdentifier('dpendencytest.Found'));
$this->assertEquals('dpendencytest.Found', $this->manager->getNormalizedIdentifier('dpendencytest.Found'));
$this->assertEquals('Winter.NoUpdate', $this->manager->normalizeIdentifier('Winter.NoUpdate'));
$this->assertEquals('Winter.NoUpdate', $this->manager->getNormalizedIdentifier('Winter.NoUpdate'));
// test multiple mixed case installed plugin
$this->assertEquals('Winter.NoUpdates', $this->manager->normalizeIdentifier('Winter.NoUpdates'));
$this->assertEquals('Winter.NoUpdates', $this->manager->normalizeIdentifier('winter.noUpdates'));
$this->assertEquals('Winter.NoUpdates', $this->manager->normalizeIdentifier('winter.noupdates'));
$this->assertEquals('Winter.NoUpdates', $this->manager->getNormalizedIdentifier('Winter.NoUpdates'));
$this->assertEquals('Winter.NoUpdates', $this->manager->getNormalizedIdentifier('winter.noUpdates'));
$this->assertEquals('Winter.NoUpdates', $this->manager->getNormalizedIdentifier('winter.noupdates'));
// test multiple mixed case not installed plugin
$this->assertEquals('Winter.MissingPlugin', $this->manager->normalizeIdentifier('Winter.MissingPlugin'));
$this->assertEquals('Winter.Missingplugin', $this->manager->normalizeIdentifier('Winter.Missingplugin'));
$this->assertEquals('Winter.missingplugin', $this->manager->normalizeIdentifier('Winter.missingplugin'));
$this->assertEquals('winter.missingplugin', $this->manager->normalizeIdentifier('winter.missingplugin'));
$this->assertEquals('Winter.MissingPlugin', $this->manager->getNormalizedIdentifier('Winter.MissingPlugin'));
$this->assertEquals('Winter.Missingplugin', $this->manager->getNormalizedIdentifier('Winter.Missingplugin'));
$this->assertEquals('Winter.missingplugin', $this->manager->getNormalizedIdentifier('Winter.missingplugin'));
$this->assertEquals('winter.missingplugin', $this->manager->getNormalizedIdentifier('winter.missingplugin'));
// test passing plugin object
$plugin = $this->manager->findByIdentifier('Winter.NoUpdates');
$this->assertInstanceOf(PluginBase::class, $plugin);
$this->assertEquals('Winter.NoUpdates', $this->manager->getNormalizedIdentifier($plugin));
}
public function testSortPluginDependencies()
{
$result = $this->manager->getPlugins();
$this->assertGreaterThan(
array_search('DependencyTest.Dependency', array_keys($result)),
array_search('DependencyTest.Found', array_keys($result))
);
// check to make sure dependency comes first and didn't stay in alphanumeric order.
$this->assertGreaterThan(
array_search('DependencyTest.Dependency', array_keys($result)),
array_search('DependencyTest.Acme', array_keys($result))
);
}
}

View File

@@ -0,0 +1,341 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\TestCase;
use Winter\Storm\Argon\Argon;
use System\Classes\SourceManifest;
use System\Classes\FileManifest;
class SourceManifestTest extends TestCase
{
/** @var SourceManifest instance */
protected $sourceManifest;
/** @var array Emulated builds from the manifest fixture */
protected $builds;
public function setUp(): void
{
parent::setUp();
$this->builds = [
'1.0.0' => new FileManifest(
base_path('modules/system/tests/fixtures/manifest/1_0_0'),
['test', 'test2']
),
'1.0.1' => new FileManifest(
base_path('modules/system/tests/fixtures/manifest/1_0_1'),
['test', 'test2']
),
'1.0.2' => new FileManifest(
base_path('modules/system/tests/fixtures/manifest/1_0_2'),
['test', 'test2']
),
'1.1.0' => new FileManifest(
base_path('modules/system/tests/fixtures/manifest/1_1_0'),
['test', 'test2', 'test3']
),
'1.1.1' => new FileManifest(
base_path('modules/system/tests/fixtures/manifest/1_1_1'),
['test', 'test2', 'test3']
),
'1.0.3' => new FileManifest(
base_path('modules/system/tests/fixtures/manifest/1_0_3'),
['test', 'test2']
),
];
$this->sourceManifest = new SourceManifest($this->manifestPath(), $this->forksPath(), false);
}
public function tearDown(): void
{
Argon::setTestNow();
$this->deleteManifest();
}
public function testCreateManifest()
{
// Freeze date for test
$testDate = Argon::create(2020, 12, 16, 12, 01, 0, 'UTC');
Argon::setTestNow($testDate);
$this->createManifest(true);
$this->assertEquals(
'{' . "\n" .
' "_description": "This is the source manifest of changes to Winter CMS for each version. This is used to determine which version of Winter CMS is in use, via the \"winter:version\" Artisan command.",' . "\n" .
' "_created": "2020-12-16T12:01:00+00:00",' . "\n" .
' "manifest": [' . "\n" .
' {' . "\n" .
' "build": "1.0.0",' . "\n" .
' "parent": null,' . "\n" .
' "modules": {' . "\n" .
' "test": "e1d6c6e4c482688e231ee37d89668268426512013695de47bfcb424f9a645c7b",' . "\n" .
' "test2": "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"' . "\n" .
' },' . "\n" .
' "files": {' . "\n" .
' "added": {' . "\n" .
' "\/modules\/test\/file1.php": "6f9b0b94528a85b2a6bb67b5621e074aef1b4c9fc9ee3ea1bd69100ea14cb3db"' . "\n" .
' }' . "\n" .
' }' . "\n" .
' },' . "\n" .
' {' . "\n" .
' "build": "1.0.1",' . "\n" .
' "parent": "1.0.0",' . "\n" .
' "modules": {' . "\n" .
' "test": "c0b794ff210862a4ce16223802efe6e28969f5a4fb42480ec8c2fef2da23d181",' . "\n" .
' "test2": "32c9f2fb6e0a22dde288a0fe1e4834798360b25e5a91d2597409d9302221381d"' . "\n" .
' },' . "\n" .
' "files": {' . "\n" .
' "added": {' . "\n" .
' "\/modules\/test\/file2.php": "96ae9f6b6377ad29226ea169f952de49fc29ae895f18a2caed76aeabdf050f1b",' . "\n" .
' "\/modules\/test2\/file1.php": "94bd47b1ac7b2837b31883ebcd38c8101687321f497c3c4b9744f68ae846721d"' . "\n" .
' }' . "\n" .
' }' . "\n" .
' },' . "\n" .
' {' . "\n" .
' "build": "1.0.2",' . "\n" .
' "parent": "1.0.1",' . "\n" .
' "modules": {' . "\n" .
' "test": "419a3c073a4296213cdc9319cfc488383753e2e81cefa1c73db38749b82a3c51",' . "\n" .
' "test2": "32c9f2fb6e0a22dde288a0fe1e4834798360b25e5a91d2597409d9302221381d"' . "\n" .
' },' . "\n" .
' "files": {' . "\n" .
' "added": {' . "\n" .
' "\/modules\/test\/file3.php": "7f4132b05911a6b0df4d41bf5dc3d007786b63a5a22daf3060ed222816d57b54"' . "\n" .
' },' . "\n" .
' "modified": {' . "\n" .
' "\/modules\/test\/file2.php": "2c61b2f5688275574251a19a57e06a4eb9e537b3916ebf6f71768e184a4ae538"' . "\n" .
' },' . "\n" .
' "removed": [' . "\n" .
' "\/modules\/test\/file1.php"' . "\n" .
' ]' . "\n" .
' }' . "\n" .
' },' . "\n" .
' {' . "\n" .
' "build": "1.0.3",' . "\n" .
' "parent": "1.0.2",' . "\n" .
' "modules": {' . "\n" .
' "test": "5316f172ac24aaa7713c97885e2e27f5c5c1e02f96fcb8e269903d737d52c7bd",' . "\n" .
' "test2": "18fc53cb280cc7e43d47e55a66b1245992c331f368106c13024572dc9fb215f7"' . "\n" .
' },' . "\n" .
' "files": {' . "\n" .
' "modified": {' . "\n" .
' "\/modules\/test2\/file1.php": "e284e816365653b5f7ddfca9319c24716cadfa3538e0c79994f0416e964da513"' . "\n" .
' },' . "\n" .
' "removed": [' . "\n" .
' "\/modules\/test\/file3.php"' . "\n" .
' ]' . "\n" .
' }' . "\n" .
' },' . "\n" .
' {' . "\n" .
' "build": "1.1.0",' . "\n" .
' "parent": "1.0.2",' . "\n" .
' "modules": {' . "\n" .
' "test": "e30811c9ad3119394edefd2f2fc0bae5fc08e2a03220ccc94e2a3f078da0df6d",' . "\n" .
' "test2": "2066b3cffe4f03b06c3399dba4d56147e4247b8071e19fa597a2e15dec697f5e",' . "\n" .
' "test3": "3679fa86c8d92a4474a01fc41abc24ebf34b847d7288434028a0559a10ff5d33"' . "\n" .
' },' . "\n" .
' "files": {' . "\n" .
' "added": {' . "\n" .
' "\/modules\/test\/file4.php": "18047babf2625ed8f42d779f14539449e23ccfdc92f79818f291eb1c55ff0533",' . "\n" .
' "\/modules\/test3\/file1.php": "1b691b48b8247af3dd8046a70e5678b77e66ada13b86c3a269a6428425c3a835"' . "\n" .
' },' . "\n" .
' "modified": {' . "\n" .
' "\/modules\/test\/file3.php": "65b1d6f8a1a0f2dfbe870faba9fa2ee0b6f4c291656df6cd66158e166a2680eb",' . "\n" .
' "\/modules\/test2\/file1.php": "5197bcc06443799c71fea8cb608cee61d9edd611ea23ea603aa0420393e014ca"' . "\n" .
' },' . "\n" .
' "removed": [' . "\n" .
' "\/modules\/test\/file2.php"' . "\n" .
' ]' . "\n" .
' }' . "\n" .
' },' . "\n" .
' {' . "\n" .
' "build": "1.1.1",' . "\n" .
' "parent": "1.1.0",' . "\n" .
' "modules": {' . "\n" .
' "test": "e30811c9ad3119394edefd2f2fc0bae5fc08e2a03220ccc94e2a3f078da0df6d",' . "\n" .
' "test2": "18fc53cb280cc7e43d47e55a66b1245992c331f368106c13024572dc9fb215f7",' . "\n" .
' "test3": "728ff099502e8d40cc6a432fdeba7185b9cb5cb530dcd8f5741ec56fcd37d189"' . "\n" .
' },' . "\n" .
' "files": {' . "\n" .
' "added": {' . "\n" .
' "\/modules\/test3\/file2.php": "0614afe438a4e74547a7036ef895008c36895a783eb8e0b458691f631138d310"' . "\n" .
' },' . "\n" .
' "modified": {' . "\n" .
' "\/modules\/test2\/file1.php": "e284e816365653b5f7ddfca9319c24716cadfa3538e0c79994f0416e964da513",' . "\n" .
' "\/modules\/test3\/file1.php": "d9aeb0853bc0f21a836f2d1cd5fc224d64ce8ea9e7e22c43b1a00f25d37c42f8"' . "\n" .
' }' . "\n" .
' }' . "\n" .
' }' . "\n" .
' ]' . "\n" .
'}',
file_get_contents($this->manifestPath())
);
}
public function testGetBuilds()
{
$this->createManifest();
$buildKeys = $this->sourceManifest->getBuilds();
$this->assertCount(6, $buildKeys);
$this->assertEquals(['1.0.0', '1.0.1', '1.0.2', '1.0.3', '1.1.0', '1.1.1'], $buildKeys);
}
public function testGetState()
{
$this->createManifest();
$this->assertEquals([
'/modules/test/file1.php' => '6f9b0b94528a85b2a6bb67b5621e074aef1b4c9fc9ee3ea1bd69100ea14cb3db',
], $this->sourceManifest->getState('1.0.0'));
$this->assertEquals([
'/modules/test/file1.php' => '6f9b0b94528a85b2a6bb67b5621e074aef1b4c9fc9ee3ea1bd69100ea14cb3db',
'/modules/test/file2.php' => '96ae9f6b6377ad29226ea169f952de49fc29ae895f18a2caed76aeabdf050f1b',
'/modules/test2/file1.php' => '94bd47b1ac7b2837b31883ebcd38c8101687321f497c3c4b9744f68ae846721d',
], $this->sourceManifest->getState('1.0.1'));
$this->assertEquals([
'/modules/test/file2.php' => '2c61b2f5688275574251a19a57e06a4eb9e537b3916ebf6f71768e184a4ae538',
'/modules/test/file3.php' => '7f4132b05911a6b0df4d41bf5dc3d007786b63a5a22daf3060ed222816d57b54',
'/modules/test2/file1.php' => '94bd47b1ac7b2837b31883ebcd38c8101687321f497c3c4b9744f68ae846721d',
], $this->sourceManifest->getState('1.0.2'));
$this->assertEquals([
'/modules/test/file2.php' => '2c61b2f5688275574251a19a57e06a4eb9e537b3916ebf6f71768e184a4ae538',
'/modules/test2/file1.php' => 'e284e816365653b5f7ddfca9319c24716cadfa3538e0c79994f0416e964da513',
], $this->sourceManifest->getState('1.0.3'));
$this->assertEquals([
'/modules/test/file3.php' => '65b1d6f8a1a0f2dfbe870faba9fa2ee0b6f4c291656df6cd66158e166a2680eb',
'/modules/test/file4.php' => '18047babf2625ed8f42d779f14539449e23ccfdc92f79818f291eb1c55ff0533',
'/modules/test2/file1.php' => '5197bcc06443799c71fea8cb608cee61d9edd611ea23ea603aa0420393e014ca',
'/modules/test3/file1.php' => '1b691b48b8247af3dd8046a70e5678b77e66ada13b86c3a269a6428425c3a835',
], $this->sourceManifest->getState('1.1.0'));
$this->assertEquals([
'/modules/test/file3.php' => '65b1d6f8a1a0f2dfbe870faba9fa2ee0b6f4c291656df6cd66158e166a2680eb',
'/modules/test/file4.php' => '18047babf2625ed8f42d779f14539449e23ccfdc92f79818f291eb1c55ff0533',
'/modules/test2/file1.php' => 'e284e816365653b5f7ddfca9319c24716cadfa3538e0c79994f0416e964da513',
'/modules/test3/file1.php' => 'd9aeb0853bc0f21a836f2d1cd5fc224d64ce8ea9e7e22c43b1a00f25d37c42f8',
'/modules/test3/file2.php' => '0614afe438a4e74547a7036ef895008c36895a783eb8e0b458691f631138d310',
], $this->sourceManifest->getState('1.1.1'));
}
public function testCompare()
{
$this->createManifest();
$this->assertEquals([
'build' => '1.0.0',
'modified' => false,
'confident' => true
], $this->sourceManifest->compare($this->builds['1.0.0']));
$this->assertEquals([
'build' => '1.0.1',
'modified' => false,
'confident' => true
], $this->sourceManifest->compare($this->builds['1.0.1']));
$this->assertEquals([
'build' => '1.0.2',
'modified' => false,
'confident' => true
], $this->sourceManifest->compare($this->builds['1.0.2']));
$this->assertEquals([
'build' => '1.0.3',
'modified' => false,
'confident' => true
], $this->sourceManifest->compare($this->builds['1.0.3']));
$this->assertEquals([
'build' => '1.1.0',
'modified' => false,
'confident' => true
], $this->sourceManifest->compare($this->builds['1.1.0']));
$this->assertEquals([
'build' => '1.1.1',
'modified' => false,
'confident' => true
], $this->sourceManifest->compare($this->builds['1.1.1']));
}
public function testCompareModified()
{
$this->createManifest();
// Hot-swap "tests/fixtures/manifest/1_1_1/modules/test/file3.php"
$old = file_get_contents(base_path('modules/system/tests/fixtures/manifest/1_1_1/modules/test/file3.php'));
file_put_contents(base_path('modules/system/tests/fixtures/manifest/1_1_1/modules/test/file3.php'), '<?php // Changed');
$modifiedManifest = new FileManifest(base_path('modules/system/tests/fixtures/manifest/1_1_1'), ['test', 'test2']);
$modifiedManifest->getFiles();
file_put_contents(base_path('modules/system/tests/fixtures/manifest/1_1_1/modules/test/file3.php'), $old);
$this->assertEquals([
'build' => '1.1.1',
'modified' => true,
'confident' => true,
], $this->sourceManifest->compare($modifiedManifest));
}
public function testCompareModifiedSecondBranch()
{
$this->createManifest();
// Add "tests/fixtures/manifest/1_0_3/modules/test/file3.php"
file_put_contents(base_path('modules/system/tests/fixtures/manifest/1_0_3/modules/test/file3.php'), '<?php // Changed');
$modifiedManifest = new FileManifest(base_path('modules/system/tests/fixtures/manifest/1_0_3'), ['test', 'test2']);
$modifiedManifest->getFiles();
unlink(base_path('modules/system/tests/fixtures/manifest/1_0_3/modules/test/file3.php'));
$this->assertEquals([
'build' => '1.0.3',
'modified' => true,
'confident' => false, // 50% match, not confident
], $this->sourceManifest->compare($modifiedManifest));
}
protected function createManifest(bool $write = false)
{
$this->deleteManifest();
foreach ($this->builds as $build => $fileManifest) {
$this->sourceManifest->addBuild($build, $fileManifest);
}
if ($write) {
file_put_contents($this->manifestPath(), $this->sourceManifest->generate());
}
$this->sourceManifest->loadForks();
}
protected function deleteManifest()
{
if (file_exists($this->manifestPath())) {
unlink($this->manifestPath());
}
}
protected function manifestPath()
{
return base_path('modules/system/tests/fixtures/manifest/builds.json');
}
protected function forksPath()
{
return base_path('modules/system/tests/fixtures/manifest/forks.json');
}
}

View File

@@ -0,0 +1,258 @@
<?php
namespace System\Tests\Classes;
use System\Tests\Bootstrap\TestCase;
use System\Classes\VersionManager;
class VersionManagerTest extends TestCase
{
public function setUp() : void
{
parent::setUp();
include_once base_path() . '/modules/system/tests/fixtures/plugins/winter/tester/Plugin.php';
include_once base_path() . '/modules/system/tests/fixtures/plugins/winter/sample/Plugin.php';
include_once base_path() . '/modules/system/tests/fixtures/plugins/winter/noupdates/Plugin.php';
}
//
// Tests
//
public function testGetLatestFileVersion()
{
$manager = VersionManager::instance();
$result = self::callProtectedMethod($manager, 'getLatestFileVersion', ['\Winter\\Tester']);
$this->assertNotNull($result);
$this->assertEquals('1.5.1', $result);
}
public function testGetFileVersions()
{
$manager = VersionManager::instance();
$result = self::callProtectedMethod($manager, 'getFileVersions', ['\Winter\\Tester']);
$this->assertCount(13, $result);
$this->assertArrayHasKey('1.0.1', $result);
$this->assertArrayHasKey('1.0.2', $result);
$this->assertArrayHasKey('1.0.3', $result);
$this->assertArrayHasKey('1.0.4', $result);
$this->assertArrayHasKey('1.0.5', $result);
$this->assertArrayHasKey('1.1.0', $result);
$this->assertArrayHasKey('1.2.0', $result);
$this->assertArrayHasKey('1.3.0', $result);
$this->assertArrayHasKey('1.3.1', $result);
$this->assertArrayHasKey('1.3.2', $result);
$this->assertArrayHasKey('1.4.1', $result);
$this->assertArrayHasKey('1.5.0', $result);
$this->assertArrayHasKey('1.5.1', $result);
$sample = $result['1.0.1'];
$this->assertEquals('Added some upgrade file and some "seeding"', $sample[0]);
$sample = $result['1.1.0'];
$this->assertEquals('!!! Drop support for blog settings', $sample[0]);
$this->assertEquals('drop_blog_settings_table.php', $sample[1]);
$sample = $result['1.2.0'];
$this->assertEquals('!!! Security update - see: https://wintercms.com', $sample[0]);
$sample = $result['1.3.0'];
$this->assertEquals('!!! We\'ve refactored major parts of this plugin. Please see the website for more information.', $sample);
$sample = $result['1.3.1'];
$this->assertEquals('Minor bug fix Please see changelog', $sample[0]);
$this->assertEquals('fix_database.php', $sample[1]);
$sample = $result['1.3.2'];
$this->assertEquals('Added support for Translate plugin. Added some new languages.', $sample);
$sample = $result['1.5.0'];
$this->assertEquals('!!! Another major update to fix several issues', $sample);
$sample = $result['1.5.1'];
$this->assertEquals('Improved signature with the Test::method()', $sample[0]);
$this->assertEquals('Translation updates.', $sample[1]);
/*
* Test junk file
*/
$result = self::callProtectedMethod($manager, 'getFileVersions', ['\Winter\\Sample']);
$this->assertCount(5, $result);
$this->assertArrayHasKey('junk', $result);
$this->assertArrayHasKey('1', $result);
$this->assertArrayHasKey('1.0.*', $result);
$this->assertArrayHasKey('1.0.x', $result);
$this->assertArrayHasKey('10.3', $result);
$sample = array_shift($result);
$comment = array_shift($sample);
$this->assertEquals("JUNK JUNK JUNK", $comment);
/*
* Test empty file
*/
$result = self::callProtectedMethod($manager, 'getFileVersions', ['\Winter\\NoUpdates']);
$this->assertEmpty($result);
}
public function testGetNewFileVersions()
{
$manager = VersionManager::instance();
$result = self::callProtectedMethod($manager, 'getNewFileVersions', ['\Winter\\Tester', '1.0.3']);
$this->assertCount(10, $result);
$this->assertArrayHasKey('1.0.4', $result);
$this->assertArrayHasKey('1.0.5', $result);
$this->assertArrayHasKey('1.1.0', $result);
$this->assertArrayHasKey('1.2.0', $result);
$this->assertArrayHasKey('1.3.0', $result);
$this->assertArrayHasKey('1.3.1', $result);
$this->assertArrayHasKey('1.3.2', $result);
$this->assertArrayHasKey('1.4.1', $result);
$this->assertArrayHasKey('1.5.0', $result);
$this->assertArrayHasKey('1.5.1', $result);
/*
* When at version 0, should return everything
*/
$manager = VersionManager::instance();
$result = self::callProtectedMethod($manager, 'getNewFileVersions', ['\Winter\\Tester']);
$this->assertCount(13, $result);
$this->assertArrayHasKey('1.0.1', $result);
$this->assertArrayHasKey('1.0.2', $result);
$this->assertArrayHasKey('1.0.3', $result);
$this->assertArrayHasKey('1.0.4', $result);
$this->assertArrayHasKey('1.0.5', $result);
$this->assertArrayHasKey('1.1.0', $result);
$this->assertArrayHasKey('1.2.0', $result);
$this->assertArrayHasKey('1.3.0', $result);
$this->assertArrayHasKey('1.3.1', $result);
$this->assertArrayHasKey('1.3.2', $result);
$this->assertArrayHasKey('1.4.1', $result);
$this->assertArrayHasKey('1.5.0', $result);
$this->assertArrayHasKey('1.5.1', $result);
}
/**
* @dataProvider versionInfoProvider
*
* @param $versionInfo
* @param $expectedComments
* @param $expectedScripts
*/
public function testExtractScriptsAndComments($versionInfo, $expectedComments, $expectedScripts)
{
$manager = VersionManager::instance();
list($comments, $scripts) = self::callProtectedMethod($manager, 'extractScriptsAndComments', [$versionInfo]);
$this->assertIsArray($comments);
$this->assertIsArray($scripts);
$this->assertEquals($expectedComments, $comments);
$this->assertEquals($expectedScripts, $scripts);
}
public function versionInfoProvider()
{
return [
[
'A single update comment string',
[
'A single update comment string'
],
[]
],
[
[
'A classic update comment string followed by script',
'update_script.php'
],
[
'A classic update comment string followed by script'
],
[
'update_script.php'
]
],
[
[
'scripts_can_go_first.php',
'An update comment string after the script',
],
[
'An update comment string after the script'
],
[
'scripts_can_go_first.php'
]
],
[
[
'scripts_can_go_first.php',
'An update comment string after the script',
'scripts_can_go_anywhere.php',
],
[
'An update comment string after the script'
],
[
'scripts_can_go_first.php',
'scripts_can_go_anywhere.php'
]
],
[
[
'scripts_can_go_first.php',
'The first update comment',
'scripts_can_go_anywhere.php',
'The second update comment',
],
[
'The first update comment',
'The second update comment'
],
[
'scripts_can_go_first.php',
'scripts_can_go_anywhere.php'
]
],
[
[
'file.name.with.dots.php',
'The first update comment',
'1.0.2.scripts_can_go_anywhere.php',
'The second update comment',
],
[
'The first update comment',
'The second update comment'
],
[
'file.name.with.dots.php',
'1.0.2.scripts_can_go_anywhere.php'
]
],
[
[
'subdirectory/file.name.with.dots.php',
'The first update comment',
'subdirectory\1.0.2.scripts_can_go_anywhere.php',
'The second update comment',
],
[
'The first update comment',
'The second update comment'
],
[
'subdirectory/file.name.with.dots.php',
'subdirectory\1.0.2.scripts_can_go_anywhere.php'
]
]
];
}
}

View File

@@ -0,0 +1,232 @@
<?php
namespace System\Tests\Classes\Asset;
use System\Classes\Asset\BundleManager;
use System\Tests\Bootstrap\TestCase;
class BundleManagerTest extends TestCase
{
protected BundleManager $bundleManager;
public function setUp(): void
{
parent::setUp();
// Reset the bundle manager
BundleManager::forgetInstance();
// Bind the instance for convenience
$this->bundleManager = BundleManager::instance();
}
/**
* Test the registerBundles & registerBundle functions correctly allow the user to append bundles
*/
public function testRegisterBundles(): void
{
$defaultBundles = $this->bundleManager->getBundles();
$this->bundleManager->registerBundles([
'winter1js' => [
'winter1js' => 'v1.0.0'
]
]);
$this->bundleManager->registerBundle('winter2js', [
'winter2js' => 'v1.0.0'
]);
$bundles = $this->bundleManager->getBundles();
$this->assertCount(count($defaultBundles) + 2, $bundles);
$this->assertContains('winter1js', $bundles);
$this->assertContains('winter2js', $bundles);
}
/**
* This test ensures that defining new bundles does not affect the default bundles
*/
public function testRegisterBundlesDoesNotBreakDefaults(): void
{
// Get the default bundles so we can validate them existing later
$defaultBundles = $this->bundleManager->getBundles();
// Flush the instance as if we have just booted
BundleManager::forgetInstance();
$this->bundleManager = BundleManager::instance();
// Register a new bundle
$this->bundleManager->registerBundles([
'winterjs' => [
'winterjs' => 'v1.0.0'
]
]);
// Grab the current bundles
$bundles = $this->bundleManager->getBundles();
// Validate that all default bundles have been registered when adding a new bundle
foreach ($defaultBundles as $defaultBundle) {
$this->assertContains($defaultBundle, $bundles);
}
}
/**
* Test the forcing a package to a new version
*/
public function testRegisterBundlesOverrideDefault(): void
{
$tailwindPackages = $this->bundleManager->getBundlePackages('tailwind', 'mix');
$this->bundleManager->registerBundle('tailwind', [
'tailwindcss' => 'dev-999.999.999',
'@tailwindcss/forms' => 'dev-999.999.999',
'@tailwindcss/typography' => 'dev-999.999.999',
]);
$updatedTailwindPackages = $this->bundleManager->getBundlePackages('tailwind', 'mix');
foreach ($tailwindPackages as $tailwindPackage => $version) {
$this->assertArrayHasKey($tailwindPackage, $updatedTailwindPackages);
$this->assertNotEquals($updatedTailwindPackages[$tailwindPackage], $version);
}
}
/**
* Test getting all supported bundles
*/
public function testGetBundles(): void
{
$bundles = $this->bundleManager->getBundles();
$this->assertIsArray($bundles);
$count = count($bundles);
$this->bundleManager->registerBundle('test', [
'a' => 'v0.1.2',
]);
$this->assertCount($count + 1, $this->bundleManager->getBundles());
}
/**
* Test getting bundle packages works and allows for config overloading
*/
public function testGetBundlePackages(): void
{
// Test that getting a default returns an array and validate one of the packages
$packages = $this->bundleManager->getBundlePackages('tailwind', 'mix');
$this->assertIsArray($packages);
$this->assertArrayHasKey('tailwindcss', $packages);
// Test that getting a package with compiler dependant packages does not return the package for invalid compiler
$packages = $this->bundleManager->getBundlePackages('vue', 'mix');
$this->assertIsArray($packages);
$this->assertArrayNotHasKey('@vitejs/plugin-vue', $packages);
// Test that getting a package with compiler dependant packages does return the package for a valid compiler
$packages = $this->bundleManager->getBundlePackages('vue', 'vite');
$this->assertIsArray($packages);
$this->assertArrayHasKey('@vitejs/plugin-vue', $packages);
// Validate that `testing` does not exist
$packages = $this->bundleManager->getBundlePackages('testing', 'vite');
$this->assertIsArray($packages);
$this->assertEmpty($packages);
$this->bundleManager->registerBundle('testing', [
'a' => 'v0.1.2',
'mix' => [
'b' => 'v0.1.3',
]
]);
// Validate the testing bundle works with compiler dependent packages
$packages = $this->bundleManager->getBundlePackages('testing', 'vite');
$this->assertIsArray($packages);
$this->assertCount(1, $packages);
$packages = $this->bundleManager->getBundlePackages('testing', 'mix');
$this->assertIsArray($packages);
$this->assertCount(2, $packages);
}
/**
* Test the AssetBundles setup handlers functionality
*/
public function testSetupHandlers(): void
{
// Test that the default handler is accessible
$handler = $this->bundleManager->getSetupHandler('tailwind');
$this->assertIsCallable($handler);
// Add a new handler
$this->bundleManager->registerSetupHandler('testing', fn () => true);
$handler = $this->bundleManager->getSetupHandler('testing');
$this->assertIsCallable($handler);
// Validate that handler returned is ours
$this->assertTrue($handler());
// Override the handler
$this->bundleManager->registerSetupHandler('testing', fn () => false);
$handler = $this->bundleManager->getSetupHandler('testing');
$this->assertIsCallable($handler);
$this->assertFalse($handler());
}
/**
* Test the AssetBundles scaffold handlers functionality
*/
public function testScaffoldHandlers(): void
{
// Test that the default handler is accessible
$handler = $this->bundleManager->getScaffoldHandler('tailwind');
$this->assertIsCallable($handler);
// Add a new handler
$this->bundleManager->registerScaffoldHandler('testing', fn () => true);
$handler = $this->bundleManager->getScaffoldHandler('testing');
$this->assertIsCallable($handler);
// Validate that handler returned is ours
$this->assertTrue($handler());
// Override the handler
$this->bundleManager->registerScaffoldHandler('testing', fn () => false);
$handler = $this->bundleManager->getScaffoldHandler('testing');
$this->assertIsCallable($handler);
$this->assertFalse($handler());
}
/**
* Validate that when registered a callable is added to the `callbacks` array within the BundleManager
*/
public function testRegisterCallback(): void
{
$this->expectExceptionMessage('callback registered');
$this->bundleManager->registerCallback(function (BundleManager $manager) {
throw new \RuntimeException('callback registered');
});
}
/**
* Test that calling registerBundles within registerCallback functions correctly
*/
public function testLoadRegisteredBundles(): void
{
$this->bundleManager->registerCallback(function (BundleManager $manager) {
$manager->registerBundles([
'winter-test-js' => [
'test-package' => 'v0.1.2',
'vite' => [
'vite-package' => 'v0.1.3',
]
]
]);
});
$this->assertContains('winter-test-js', $this->bundleManager->getBundles());
$bundle = $this->bundleManager->getBundlePackages('winter-test-js', 'mix');
$this->assertArrayHasKey('test-package', $bundle);
$this->assertArrayNotHasKey('vite-package', $bundle);
$bundle = $this->bundleManager->getBundlePackages('winter-test-js', 'vite');
$this->assertArrayHasKey('test-package', $bundle);
$this->assertArrayHasKey('vite-package', $bundle);
}
}

View File

@@ -0,0 +1,469 @@
<?php
namespace System\Tests\Classes\Asset;
use System\Classes\Asset\PackageJson;
use System\Tests\Bootstrap\TestCase;
class PackageJsonTest extends TestCase
{
/**
* Default test file fixture
*/
protected string $testFile;
/**
* Bind the default test file fixture
*/
public function setUp(): void
{
parent::setUp();
$this->testFile = __DIR__ . '/../../fixtures/npm/package-test.json';
}
/**
* Test loading a package.json file from path
*/
public function testLoadFile(): void
{
$packageJson = new PackageJson($this->testFile);
$contents = $packageJson->getContents();
$this->assertArrayHasKey('workspaces', $contents);
$this->assertArrayHasKey('packages', $contents['workspaces']);
$this->assertIsArray($contents['workspaces']['packages']);
}
/**
* Test loading a corrupted package.json file correctly errors
*/
public function testLoadCorruptFile(): void
{
$this->expectException(\JsonException::class);
new PackageJson(__DIR__ . '/../../fixtures/npm/package-corrupt.json');
}
/**
* Test creating an instance with non-existing file
*/
public function testNewFile(): void
{
$packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test-new.json');
$contents = $packageJson->getContents();
$this->assertIsArray($contents);
$this->assertCount(0, $contents);
}
/**
* Test creating an instance without a path
*/
public function testMemoryInstance(): void
{
$packageJson = new PackageJson();
$contents = $packageJson->getContents();
$this->assertIsArray($contents);
$this->assertCount(0, $contents);
}
/**
* Test setting and getting the name property
*/
public function testNameMethods(): void
{
$packageJson = new PackageJson();
$contents = $packageJson->getContents();
$this->assertIsArray($contents);
$this->assertCount(0, $contents);
$packageJson->setName('example-name');
$this->assertEquals('example-name', $packageJson->getName());
$contents = $packageJson->getContents();
$this->assertIsArray($contents);
$this->assertCount(1, $contents);
$this->assertArrayHasKey('name', $contents);
$this->assertEquals('example-name', $contents['name']);
}
/**
* Test getting the path of the current file
*/
public function testGetPath(): void
{
$packageJson = new PackageJson(__DIR__ . '/package.json');
$path = $packageJson->getPath();
$this->assertIsString($path);
$this->assertEquals(__DIR__ . '/package.json', $path);
$packageJson = new PackageJson();
$path = $packageJson->getPath();
$this->assertNull($path);
}
/**
* Test validating the name on set
*/
public function testNameValidation(): void
{
$packageJson = new PackageJson();
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName('Test');
}, \InvalidArgumentException::class, 'Package names must be lower case');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName('.test');
}, \InvalidArgumentException::class, 'Package names must not start with . or _');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName('_test');
}, \InvalidArgumentException::class, 'Package names must not start with . or _');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName('te~st');
}, \InvalidArgumentException::class, 'Package names must not include special characters');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName('te*st');
}, \InvalidArgumentException::class, 'Package names must not include special characters');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName('test!');
}, \InvalidArgumentException::class, 'Package names must not include special characters');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName(sprintf('te%sst', str_repeat('s', 214)));
}, \InvalidArgumentException::class, 'Package names must not be longer than 214 characters');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName('test ');
}, \InvalidArgumentException::class, 'Package names must not include whitespace');
$this->assertThrows(function () use ($packageJson) {
$packageJson->setName(' test');
}, \InvalidArgumentException::class, 'Package names must not include whitespace');
}
/**
* Test checking package workspace exists
*/
public function testHasWorkspace(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasWorkspace('themes/demo'));
$this->assertFalse($packageJson->hasWorkspace('themes/test'));
}
/**
* Test adding workspace package
*/
public function testAddWorkspace(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertFalse($packageJson->hasWorkspace('themes/test'));
$packageJson->addWorkspace('themes/test');
$this->assertTrue($packageJson->hasWorkspace('themes/test'));
// Create blank source
$packageJson = new PackageJson();
$this->assertFalse($packageJson->hasWorkspace('themes/test'));
$packageJson->addWorkspace('themes/test');
$this->assertTrue($packageJson->hasWorkspace('themes/test'));
}
/**
* Test removing workspace package
*/
public function testRemoveWorkspace(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasWorkspace('themes/demo'));
$packageJson->removeWorkspace('themes/demo');
$this->assertFalse($packageJson->hasWorkspace('themes/demo'));
// Create blank source
$packageJson = new PackageJson();
$packageJson->removeWorkspace('themes/demo');
$this->assertFalse($packageJson->hasWorkspace('themes/demo'));
}
/**
* Test when adding a workspace package, it removes the package from ignored workspace packages
*/
public function testAddWorkspaceRemovesIgnoredPackage(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertFalse($packageJson->hasWorkspace('modules/backend'));
$this->assertTrue($packageJson->hasIgnoredPackage('modules/backend'));
$packageJson->addWorkspace('modules/backend');
$this->assertTrue($packageJson->hasWorkspace('modules/backend'));
$this->assertFalse($packageJson->hasIgnoredPackage('modules/backend'));
}
/**
* Test checking ignore package workspace exists
*/
public function testHasIgnoredPackage(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasIgnoredPackage('modules/backend'));
$this->assertFalse($packageJson->hasIgnoredPackage('modules/test'));
// Create blank source
$packageJson = new PackageJson();
$this->assertFalse($packageJson->hasIgnoredPackage('modules/backend'));
$this->assertFalse($packageJson->hasIgnoredPackage('modules/test'));
}
/**
* Test adding ignore workspace package
*/
public function testAddIgnoredPackage(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertFalse($packageJson->hasIgnoredPackage('themes/example'));
$packageJson->addIgnoredPackage('themes/example');
$this->assertTrue($packageJson->hasIgnoredPackage('themes/example'));
// Create blank source
$packageJson = new PackageJson();
$packageJson->addIgnoredPackage('themes/example');
$this->assertTrue($packageJson->hasIgnoredPackage('themes/example'));
}
/**
* Test removing ignore workspace package
*/
public function testRemoveIgnoredPackage(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasIgnoredPackage('modules/system'));
$packageJson->removeIgnoredPackage('modules/system');
$this->assertFalse($packageJson->hasIgnoredPackage('modules/system'));
// Create blank source
$packageJson = new PackageJson();
$packageJson->removeIgnoredPackage('modules/system');
$this->assertFalse($packageJson->hasIgnoredPackage('modules/system'));
}
/**
* Test when adding an ignore workspace package, it removes the package from workspace packages
*/
public function testAddIgnoredPackageRemovesWorkspace(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasWorkspace('themes/demo'));
$this->assertFalse($packageJson->hasIgnoredPackage('themes/demo'));
$packageJson->addIgnoredPackage('themes/demo');
$this->assertFalse($packageJson->hasWorkspace('themes/demo'));
$this->assertTrue($packageJson->hasIgnoredPackage('themes/demo'));
}
/**
* Test checking if package.json has deps
*/
public function testHasDependency(): void
{
$packageJson = new PackageJson($this->testFile);
// Check that deps exist
$this->assertTrue($packageJson->hasDependency('test'));
$this->assertTrue($packageJson->hasDependency('test-dev'));
// Check that deps don't exist
$this->assertFalse($packageJson->hasWorkspace('test-dev2'));
$this->assertFalse($packageJson->hasWorkspace('testx'));
}
/**
* Test adding dependencies, when overwriting check that package is moved from devDeps to deps or revsersed
*
* @return void
*/
public function testAddDependency(): void
{
// Test adding packages
$packageJson = new PackageJson($this->testFile);
$packageJson->addDependency('winter', '4.0.1', dev: false)
->addDependency('winter-dev', '4.0.1', dev: true);
$this->assertArrayNotHasKey('winter', $packageJson->getContents()['devDependencies']);
$this->assertArrayHasKey('winter', $packageJson->getContents()['dependencies']);
$this->assertArrayNotHasKey('winter-dev', $packageJson->getContents()['dependencies']);
$this->assertArrayHasKey('winter-dev', $packageJson->getContents()['devDependencies']);
// Test adding packages with overwrites
$packageJson = new PackageJson($this->testFile);
// Should do nothing as test is already in deps not dev deps
$packageJson->addDependency('test', '6.0.1', dev: true, overwrite: false);
$this->assertArrayHasKey('test', $packageJson->getContents()['dependencies']);
$this->assertArrayNotHasKey('test', $packageJson->getContents()['devDependencies']);
$this->assertEquals('^1.0.0', $packageJson->getContents()['dependencies']['test']);
// Should move package from dev deps to deps with new version
$packageJson->addDependency('test', '6.0.1', dev: true, overwrite: true);
$this->assertArrayNotHasKey('test', $packageJson->getContents()['dependencies']);
$this->assertArrayHasKey('test', $packageJson->getContents()['devDependencies']);
$this->assertEquals('6.0.1', $packageJson->getContents()['devDependencies']['test']);
// Create blank source
$packageJson = new PackageJson();
// Add a non-dev dependency
$packageJson->addDependency('winter', '4.0.1', dev: false);
// Add a dev dependency
$packageJson->addDependency('winter-dev', '4.0.1', dev: true);
}
/**
* Test removing dependencies
*/
public function testRemoveDependency(): void
{
$packageJson = new PackageJson($this->testFile);
// Check package removed from dev deps
$this->assertArrayHasKey('test-dev', $packageJson->getContents()['devDependencies'] ?? []);
$packageJson->removeDependency('test-dev');
$this->assertArrayNotHasKey('test-dev', $packageJson->getContents()['devDependencies'] ?? []);
// Check package removed from deps
$this->assertArrayHasKey('test', $packageJson->getContents()['dependencies'] ?? []);
$packageJson->removeDependency('test');
$this->assertArrayNotHasKey('test', $packageJson->getContents()['dependencies'] ?? []);
// Create blank source
$packageJson = new PackageJson();
$packageJson->removeDependency('test-dev');
$this->assertArrayNotHasKey('test-dev', $packageJson->getContents()['devDependencies'] ?? []);
}
/**
* Test checking if a script exists in a package.json
*/
public function testHasScript(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasScript('foo'));
$this->assertTrue($packageJson->hasScript('example'));
$this->assertTrue($packageJson->hasScript('test'));
$this->assertFalse($packageJson->hasScript('bar'));
$this->assertFalse($packageJson->hasScript('winter'));
$this->assertFalse($packageJson->hasScript('testing'));
}
/**
* Test getting the value of a script by name
*/
public function testGetScript(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertEquals('bar ./test', $packageJson->getScript('foo'));
$this->assertEquals('example test', $packageJson->getScript('example'));
$this->assertEquals('testing', $packageJson->getScript('test'));
$packageJson = new PackageJson();
$this->assertNull($packageJson->getScript('foo'));
}
/**
* Test getting the value of a script by name
*/
public function testAddScript(): void
{
$packageJson = new PackageJson($this->testFile);
$packageJson->addScript('winter', 'winter ./testing');
$this->assertTrue($packageJson->hasScript('winter'));
$this->assertEquals('winter ./testing', $packageJson->getScript('winter'));
$contents = $packageJson->getContents();
$this->assertTrue(isset($contents['scripts']['winter']));
$this->assertEquals('winter ./testing', $contents['scripts']['winter']);
$packageJson = new PackageJson();
$packageJson->addScript('winter', 'winter ./testing');
$this->assertTrue($packageJson->hasScript('winter'));
}
/**
* Test removing scripts from package.json
*/
public function testRemoveScript(): void
{
$packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasScript('foo'));
$packageJson->removeScript('foo');
$this->assertFalse($packageJson->hasScript('foo'));
$this->assertTrue($packageJson->hasScript('example'));
$packageJson->removeScript('example');
$this->assertFalse($packageJson->hasScript('example'));
$packageJson = new PackageJson();
$packageJson->removeScript('foo');
$this->assertFalse($packageJson->hasScript('foo'));
}
/**
* Test saving, when saving with a file path set on init and passing a file path on save. Fails when no path given
*/
public function testSave(): void
{
$srcFile = $this->testFile;
$backupFile = __DIR__ . '/../../fixtures/npm/package-test.json.back';
// Backup config file
copy($srcFile, $backupFile);
// Make a change and save the file, overwriting
$packageJson = new PackageJson($srcFile);
$this->assertNull($packageJson->getName());
$packageJson->setName('testing');
$packageJson->save();
// Validate overwrite worked
$packageJson = new PackageJson($srcFile);
$this->assertEquals('testing', $packageJson->getName());
// Restore the config file
copy($backupFile, $srcFile);
// Remove backup file
unlink($backupFile);
// Test saving file to new path
$testFile = __DIR__ . '/../../fixtures/npm/package-test.json.test';
$packageJson = new PackageJson($srcFile);
$this->assertNull($packageJson->getName());
$packageJson->setName('testing');
$packageJson->save($testFile);
// Validate new file path exists and contains change
$packageJson = new PackageJson($testFile);
$this->assertEquals('testing', $packageJson->getName());
// Remove test file
unlink($testFile);
// Validate that a file save with no path throws an error
$this->assertThrows(function () {
$packageJson = new PackageJson();
$packageJson->setName('should-fail');
$packageJson->save();
}, \RuntimeException::class, 'Unable to save, no path given');
}
}