<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j.boggiano @ seld . be >
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Package\Loader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use PHPUnit\Framework\TestCase;
class ValidatingArrayLoaderTest extends TestCase
{
/**
* @dataProvider successProvider
*/
public function testLoadSuccess($config)
{
$internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock();
$internalLoader
->expects($this->once())
->method('load')
->with($config);
$loader = new ValidatingArrayLoader($internalLoader, true, null, ValidatingArrayLoader::CHECK_ALL);
$loader->load($config);
}
public function successProvider()
{
return array(
array( // minimal
array(
'name' => 'foo/bar',
),
),
array( // complete
array(
'name' => 'foo/bar',
'description' => 'Foo bar',
'version' => '1.0.0',
'type' => 'library',
'keywords' => array('a', 'b_c', 'D E', 'éîüø', '微信'),
'homepage' => 'https://foo.com',
'time' => '2010-10-10T10:10:10+00:00',
'license' => 'MIT',
'authors' => array(
array(
'name' => 'Alice',
'email' => 'alice@example.org',
'role' => 'Lead',
'homepage' => 'http://example.org',
),
array(
'name' => 'Bob',
'homepage' => '',
),
),
'support' => array(
'email' => 'mail@example.org',
'issues' => 'http://example.org/',
'forum' => 'http://example.org/',
'wiki' => 'http://example.org/',
'source' => 'http://example.org/',
'irc' => 'irc://example.org/example',
'rss' => 'http://example.org/rss',
'chat' => 'http://example.org/chat',
),
'require' => array(
'a/b' => '1.*',
'b/c' => '~2',
'example' => '>2.0-dev,< 2.4-dev ' ,
),
'require-dev' => array(
'a/b' => '1.*',
'b/c' => '*',
'example' => '>2.0-dev,< 2.4-dev ' ,
),
'conflict' => array(
'a/b' => '1.*',
'b/c' => '>2.7',
'example' => '>2.0-dev,< 2.4-dev ' ,
),
'replace' => array(
'a/b' => '1.*',
'example' => '>2.0-dev,< 2.4-dev ' ,
),
'provide' => array(
'a/b' => '1.*',
'example' => '>2.0-dev,< 2.4-dev ' ,
),
'suggest' => array(
'foo/bar' => 'Foo bar is very useful',
),
'autoload' => array(
'psr-0' => array(
'Foo\\Bar' => 'src/',
'' => 'fallback/libs/',
),
'classmap' => array(
'dir/',
'dir2/file.php',
),
'files' => array(
'functions.php',
),
),
'include-path' => array(
'lib/',
),
'target-dir' => 'Foo/Bar',
'minimum-stability' => 'dev',
'repositories' => array(
array(
'type' => 'composer',
'url' => 'https://repo.packagist.org/',
),
),
'config' => array(
'bin-dir' => 'bin',
'vendor-dir' => 'vendor',
'process-timeout' => 10000,
),
'archive' => array(
'exclude' => array('/foo/bar', 'baz', '!/foo/bar/baz'),
),
'scripts' => array(
'post-update-cmd' => 'Foo\\Bar\\Baz::doSomething',
'post-install-cmd' => array(
'Foo\\Bar\\Baz::doSomething',
),
),
'extra' => array(
'random' => array('stuff' => array('deeply' => 'nested')),
'branch-alias' => array(
'dev-master' => '2.0-dev',
'dev-old' => '1.0.x-dev',
'3.x-dev' => '3.1.x-dev',
),
),
'bin' => array(
'bin/foo',
'bin/bar',
),
'transport-options' => array('ssl' => array('local_cert' => '/opt/certs/test.pem')),
),
),
array( // test licenses as array
array(
'name' => 'foo/bar',
'license' => array('MIT', 'WTFPL'),
),
),
array( // test bin as string
array(
'name' => 'foo/bar',
'bin' => 'bin1',
),
),
);
}
/**
* @dataProvider errorProvider
*/
public function testLoadFailureThrowsException($config, $expectedErrors)
{
$internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock();
$loader = new ValidatingArrayLoader($internalLoader, true, null, ValidatingArrayLoader::CHECK_ALL);
try {
$loader->load($config);
$this->fail('Expected exception to be thrown');
} catch (InvalidPackageException $e) {
$errors = $e->getErrors();
sort($expectedErrors);
sort($errors);
$this->assertEquals($expectedErrors, $errors);
}
}
/**
* @dataProvider warningProvider
*/
public function testLoadWarnings($config, $expectedWarnings)
{
$internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock();
$loader = new ValidatingArrayLoader($internalLoader, true, null, ValidatingArrayLoader::CHECK_ALL);
$loader->load($config);
$warnings = $loader->getWarnings();
sort($expectedWarnings);
sort($warnings);
$this->assertEquals($expectedWarnings, $warnings);
}
/**
* @dataProvider warningProvider
*/
public function testLoadSkipsWarningDataWhenIgnoringErrors($config, $expectedWarnings, $mustCheck = true)
{
if (!$mustCheck) {
$this->assertTrue(true);
return;
}
$internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock();
$internalLoader
->expects($this->once())
->method('load')
->with(array('name' => 'a/b'));
$loader = new ValidatingArrayLoader($internalLoader, true, null, ValidatingArrayLoader::CHECK_ALL);
$config['name'] = 'a/b';
$loader->load($config);
}
public function errorProvider()
{
return array(
array(
array(
'name' => 'foo',
),
array(
'name : invalid value (foo), must match [A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*',
),
),
array(
array(
'name' => 'foo/bar',
'homepage' => 43,
),
array(
'homepage : should be a string, integer given',
),
),
array(
array(
'name' => 'foo/bar',
'support' => array(
'source' => array(),
),
),
array(
'support.source : invalid value, must be a string',
),
),
array(
array(
'name' => 'foo/bar',
'autoload' => 'strings',
),
array(
'autoload : should be an array, string given',
),
),
array(
array(
'name' => 'foo/bar',
'autoload' => array(
'psr0' => array(
'foo' => 'src',
),
),
),
array(
'autoload : invalid value (psr0), must be one of psr-0, psr-4, classmap, files, exclude-from-classmap',
),
),
array(
array(
'name' => 'foo/bar',
'transport-options' => 'test',
),
array(
'transport-options : should be an array, string given',
),
),
);
}
public function warningProvider()
{
return array(
array(
array(
'name' => 'foo/bar',
'homepage' => 'foo:bar',
),
array(
'homepage : invalid value (foo:bar), must be an http/https URL',
),
),
array(
array(
'name' => 'foo/bar',
'support' => array(
'source' => 'foo:bar',
'forum' => 'foo:bar',
'issues' => 'foo:bar',
'wiki' => 'foo:bar',
'chat' => 'foo:bar',
),
),
array(
'support.source : invalid value (foo:bar), must be an http/https URL',
'support.forum : invalid value (foo:bar), must be an http/https URL',
'support.issues : invalid value (foo:bar), must be an http/https URL',
'support.wiki : invalid value (foo:bar), must be an http/https URL',
'support.chat : invalid value (foo:bar), must be an http/https URL',
),
),
array(
array(
'name' => 'foo/bar',
'require' => array(
'foo/baz' => '*',
'bar/baz' => '>=1.0',
'bar/foo' => 'dev-master',
'bar/hacked' => '@stable',
'bar/woo' => '1.0.0',
),
),
array(
'require.foo/baz : unbound version constraints (*) should be avoided',
'require.bar/baz : unbound version constraints (>=1.0) should be avoided',
'require.bar/foo : unbound version constraints (dev-master) should be avoided',
'require.bar/hacked : unbound version constraints (@stable) should be avoided',
'require.bar/woo : exact version constraints (1.0.0) should be avoided if the package follows semantic versioning',
),
false,
),
array(
array(
'name' => 'foo/bar',
'require' => array(
'Foo/Baz' => '^1.0',
),
),
array(
'Deprecation warning: require.Foo/Baz is invalid, it should not contain uppercase characters. Please use foo/baz instead. Make sure you fix this as Composer 2.0 will error.',
),
false,
),
array(
array(
'name' => 'foo/bar',
'require' => array(
'bar/unstable' => '0.3.0',
),
),
array(
// using an exact version constraint for an unstable version should not trigger a warning
),
false,
),
array(
array(
'name' => 'foo/bar',
'extra' => array(
'branch-alias' => array(
'5.x-dev' => '3.1.x-dev',
),
),
),
array(
'extra.branch-alias.5.x-dev : the target branch (3.1.x-dev) is not a valid numeric alias for this version',
),
false,
),
array(
array(
'name' => 'foo/bar',
'extra' => array(
'branch-alias' => array(
'5.x-dev' => '3.1-dev',
),
),
),
array(
'extra.branch-alias.5.x-dev : the target branch (3.1-dev) is not a valid numeric alias for this version',
),
false,
),
);
}
}