=1.0')); ok(!Semver::satisfies('0.9.0', '>=1.0')); ok(Semver::satisfies('1.2.3', '^1.2')); ok(!Semver::satisfies('2.0.0', '^1.2')); ok(Semver::satisfies('1.2.9', '~1.2.3')); ok(!Semver::satisfies('1.3.0', '~1.2.3')); ok(Semver::satisfies('1.0.0', '*')); eq(0, Semver::compare('1.0.0', '1.0.0')); ok(Semver::compare('1.0.1', '1.0.0') > 0); ok(Semver::compare('1.0.0-beta', '1.0.0') < 0); }); Harness::it('manifiesto válido parsea identidad, componentes y default', function () { $m = Manifest::fromArray([ 'name' => 'http', 'version' => '1.2.0', 'description' => 'Utilidades HTTP', 'components' => [ 'curlRequest' => ['files' => ['src/Libs/CurlRequest.php']], 'aria2' => ['files' => ['src/Libs/Aria2.php']], ], 'default' => ['curlRequest', 'aria2'], ]); eq('http', $m->name); eq('1.2.0', $m->version); eq(['curlRequest', 'aria2'], $m->expand(null)); eq(['aria2'], $m->expand('aria2')); }); Harness::it('manifiesto sin name es inválido', function () { throws(fn () => Manifest::fromArray(['version' => '1.0.0', 'components' => ['a' => ['files' => ['x']]]])); }); Harness::it('manifiesto sin components es inválido', function () { throws(fn () => Manifest::fromArray(['name' => 'x', 'version' => '1.0.0'])); }); Harness::it('default que referencia componente inexistente es inválido', function () { throws(fn () => Manifest::fromArray([ 'name' => 'x', 'version' => '1.0.0', 'components' => ['a' => ['files' => ['x']]], 'default' => ['nope'], ])); }); Harness::it('paquete sin default y sin componente explícito falla', function () { $m = Manifest::fromArray([ 'name' => 'x', 'version' => '1.0.0', 'components' => ['a' => ['files' => ['x']]], ]); throws(fn () => $m->expand(null)); }); Harness::it('resuelve dependencias en orden (dependencias primero)', function () { $dir = tmpdir(); write_file($dir . '/http/duckbrain.json', json_encode([ 'name' => 'http', 'version' => '1.0.0', 'components' => [ 'curlRequest' => ['files' => ['src/Libs/CurlRequest.php']], 'compreFace' => [ 'files' => ['src/Libs/CompreFace.php'], 'require' => ['http:curlRequest' => '>=1.0'], ], ], ], JSON_PRETTY_PRINT)); $resolver = new Resolver(new Catalog($dir)); $result = $resolver->resolve(['http:compreFace']); eq([ ['package' => 'http', 'component' => 'curlRequest'], ['package' => 'http', 'component' => 'compreFace'], ], $result); rrmdir($dir); }); Harness::it('require no satisfecho detiene con error', function () { $dir = tmpdir(); write_file($dir . '/a/duckbrain.json', json_encode([ 'name' => 'a', 'version' => '1.0.0', 'components' => [ 'main' => ['files' => ['src/Libs/A.php'], 'require' => ['b:lib' => '>=2.0']], ], ])); write_file($dir . '/b/duckbrain.json', json_encode([ 'name' => 'b', 'version' => '1.0.0', 'components' => ['lib' => ['files' => ['src/Libs/B.php']]], ])); $resolver = new Resolver(new Catalog($dir)); throws(fn () => $resolver->resolve(['a:main'])); rrmdir($dir); }); Harness::it('ciclo de dependencias se detecta', function () { $dir = tmpdir(); write_file($dir . '/a/duckbrain.json', json_encode([ 'name' => 'a', 'version' => '1.0.0', 'components' => [ 'x' => ['files' => ['src/Libs/A.php'], 'require' => ['a:y' => '*']], 'y' => ['files' => ['src/Libs/B.php'], 'require' => ['a:x' => '*']], ], ])); $resolver = new Resolver(new Catalog($dir)); throws(fn () => $resolver->resolve(['a:x'])); rrmdir($dir); }); Harness::it('la descripcion del componente prevalece y hereda del paquete', function () { $m = Manifest::fromArray([ 'name' => 'pkg', 'version' => '1.0.0', 'description' => 'Resumen del paquete', 'components' => [ 'a' => ['files' => ['src/A.php'], 'description' => 'Componente A'], 'b' => ['files' => ['src/B.php']], ], ]); eq('Componente A', $m->descriptionOf('a'), 'descripción propia gana'); eq('Resumen del paquete', $m->descriptionOf('b'), 'sin descripción propia hereda del paquete'); eq('Resumen del paquete', $m->descriptionOf(), 'sin componente: la del paquete'); $m2 = Manifest::fromArray(['name' => 'x', 'version' => '1.0.0', 'components' => ['c' => ['files' => ['c.php']]]]); eq('', $m2->descriptionOf('c'), 'paquete sin texto produce cadena vacía (no null)'); throws(fn () => Manifest::fromArray([ 'name' => 'y', 'version' => '1.0.0', 'components' => ['c' => ['files' => ['c.php'], 'description' => 7]], ]), 'description no-string es inválida'); }); Harness::it('core admite "none" como bloque con herencia completa', function () { $m = Manifest::fromArray([ 'name' => 'pkg', 'version' => '1.0.0', 'core' => 'none', 'components' => [ 'a' => ['files' => ['src/A.php']], 'b' => ['files' => ['src/B.php'], 'core' => ['requires' => '>=0.2', 'tested' => '0.2']], ], ]); ok($m->coreIndependent('a'), 'a hereda la independencia del paquete'); ok(!$m->coreIndependent('b'), 'b sobreescribe el bloque con su rango'); eq('>=0.2', $m->coreRequirement('b')); eq('0.2', $m->testedCore('b')); eq(null, $m->coreRequirement('a'), 'independiente no arrastra rangos'); $m2 = Manifest::fromArray([ 'name' => 'pkg2', 'version' => '1.0.0', 'core' => ['requires' => '>=0.1'], 'components' => ['c' => ['files' => ['src/C.php'], 'core' => 'none']], ]); ok($m2->coreIndependent('c'), 'componente declara none sobre rango de paquete'); eq(null, $m2->coreRequirement('c'), 'el rango del paquete no se filtra al bloque declarado none'); eq('>=0.1', $m2->coreRequirement()); throws(fn () => Manifest::fromArray([ 'name' => 'bad', 'version' => '1.0.0', 'core' => 'otro', 'components' => ['x' => ['files' => ['x.php']]], ]), 'core string distinto de none es inválido'); });