diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:17:31 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:17:31 +0000 |
commit | f66ab8dae2f3d0418759f81a3a64dc9517a62449 (patch) | |
tree | fbff2135e7013f196b891bbde54618eb050e4aaf /test | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-director-f66ab8dae2f3d0418759f81a3a64dc9517a62449.tar.xz icingaweb2-module-director-f66ab8dae2f3d0418759f81a3a64dc9517a62449.zip |
Adding upstream version 1.10.2.upstream/1.10.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test')
55 files changed, 5041 insertions, 0 deletions
diff --git a/test/bootstrap.php b/test/bootstrap.php new file mode 100644 index 0000000..87ed869 --- /dev/null +++ b/test/bootstrap.php @@ -0,0 +1,16 @@ +<?php + +use Icinga\Module\Director\Test\Bootstrap; + +call_user_func(function () { + $basedir = dirname(__DIR__); + if (! class_exists('PHPUnit_Framework_TestCase')) { + require_once __DIR__ . '/phpunit-compat.php'; + } + + $include_path = $basedir . '/vendor' . PATH_SEPARATOR . ini_get('include_path'); + ini_set('include_path', $include_path); + + require_once $basedir . '/library/Director/Test/Bootstrap.php'; + Bootstrap::cli($basedir); +}); diff --git a/test/config/authentication.ini b/test/config/authentication.ini new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/config/authentication.ini diff --git a/test/config/config.ini b/test/config/config.ini new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/config/config.ini diff --git a/test/config/resources.ini b/test/config/resources.ini new file mode 100644 index 0000000..1f64e52 --- /dev/null +++ b/test/config/resources.ini @@ -0,0 +1,13 @@ +[Director MySQL TestDB] +type = "db" +db = "mysql" +host = "localhost" +username = "root" +charset = "utf8" + +[Director PostgreSQL TestDB] +type = "db" +db = "pgsql" +host = "localhost" +password = "testing" +charset = "utf8" diff --git a/test/php/library/Director/Application/DependencyTest.php b/test/php/library/Director/Application/DependencyTest.php new file mode 100644 index 0000000..cc6047e --- /dev/null +++ b/test/php/library/Director/Application/DependencyTest.php @@ -0,0 +1,72 @@ +<?php + +namespace Tests\Icinga\Module\Director\Application; + +use Icinga\Module\Director\Application\Dependency; +use Icinga\Module\Director\Test\BaseTestCase; + +class DependencyTest extends BaseTestCase +{ + public function testIsNotInstalled() + { + $dependency = new Dependency('something', '>=0.3.0'); + $this->assertFalse($dependency->isInstalled()); + } + + public function testNotSatisfiedWhenNotInstalled() + { + $dependency = new Dependency('something', '>=0.3.0'); + $this->assertFalse($dependency->isSatisfied()); + } + + public function testIsInstalled() + { + $dependency = new Dependency('something', '>=0.3.0'); + $dependency->setInstalledVersion('1.10.0'); + $this->assertTrue($dependency->isInstalled()); + } + + public function testNotEnabled() + { + $dependency = new Dependency('something', '>=0.3.0'); + $this->assertFalse($dependency->isEnabled()); + } + + public function testIsEnabled() + { + $dependency = new Dependency('something', '>=0.3.0'); + $dependency->setEnabled(); + $this->assertTrue($dependency->isEnabled()); + } + + public function testNotSatisfiedWhenNotEnabled() + { + $dependency = new Dependency('something', '>=0.3.0'); + $dependency->setInstalledVersion('1.10.0'); + $this->assertFalse($dependency->isSatisfied()); + } + + public function testSatisfiedWhenEqual() + { + $dependency = new Dependency('something', '>=0.3.0'); + $dependency->setInstalledVersion('0.3.0'); + $dependency->setEnabled(); + $this->assertTrue($dependency->isSatisfied()); + } + + public function testSatisfiedWhenGreater() + { + $dependency = new Dependency('something', '>=0.3.0'); + $dependency->setInstalledVersion('0.10.0'); + $dependency->setEnabled(); + $this->assertTrue($dependency->isSatisfied()); + } + + public function testNotSatisfiedWhenSmaller() + { + $dependency = new Dependency('something', '>=20.3.0'); + $dependency->setInstalledVersion('4.999.999'); + $dependency->setEnabled(); + $this->assertFalse($dependency->isSatisfied()); + } +} diff --git a/test/php/library/Director/Application/FiltersWorkAsExpectedTest.php b/test/php/library/Director/Application/FiltersWorkAsExpectedTest.php new file mode 100644 index 0000000..216a925 --- /dev/null +++ b/test/php/library/Director/Application/FiltersWorkAsExpectedTest.php @@ -0,0 +1,15 @@ +<?php + +namespace Tests\Icinga\Module\Director\Application; + +use Icinga\Data\Filter\Filter; +use Icinga\Module\Director\Test\BaseTestCase; + +class FiltersWorkAsExpectedTest extends BaseTestCase +{ + public function testBasics() + { + $filter = Filter::fromQueryString('a'); + $this->assertTrue($filter->matches((object) ['a' => '1']), '1 is not true'); + } +} diff --git a/test/php/library/Director/Application/MemoryLimitTest.php b/test/php/library/Director/Application/MemoryLimitTest.php new file mode 100644 index 0000000..8b4301d --- /dev/null +++ b/test/php/library/Director/Application/MemoryLimitTest.php @@ -0,0 +1,67 @@ +<?php + +namespace Tests\Icinga\Module\Director\Application; + +use Icinga\Module\Director\Application\MemoryLimit; +use Icinga\Module\Director\Test\BaseTestCase; + +class MemoryLimitTest extends BaseTestCase +{ + public function testBytesValuesAreHandled() + { + $this->assertTrue(is_int(MemoryLimit::parsePhpIniByteString('1073741824'))); + $this->assertEquals( + 1073741824, + MemoryLimit::parsePhpIniByteString('1073741824') + ); + } + + public function testIntegersAreAccepted() + { + $this->assertEquals( + MemoryLimit::parsePhpIniByteString(1073741824), + 1073741824 + ); + } + + public function testNoLimitGivesMinusOne() + { + $this->assertTrue(is_int(MemoryLimit::parsePhpIniByteString('-1'))); + $this->assertEquals( + -1, + MemoryLimit::parsePhpIniByteString('-1') + ); + } + + public function testInvalidStringGivesBytes() + { + $this->assertEquals( + 1024, + MemoryLimit::parsePhpIniByteString('1024MB') + ); + } + + public function testHandlesKiloBytes() + { + $this->assertEquals( + 45 * 1024, + MemoryLimit::parsePhpIniByteString('45K') + ); + } + + public function testHandlesMegaBytes() + { + $this->assertEquals( + 512 * 1024 * 1024, + MemoryLimit::parsePhpIniByteString('512M') + ); + } + + public function testHandlesGigaBytes() + { + $this->assertEquals( + 2 * 1024 * 1024 * 1024, + MemoryLimit::parsePhpIniByteString('2G') + ); + } +} diff --git a/test/php/library/Director/CustomVariable/CustomVariablesTest.php b/test/php/library/Director/CustomVariable/CustomVariablesTest.php new file mode 100644 index 0000000..c5ba9f6 --- /dev/null +++ b/test/php/library/Director/CustomVariable/CustomVariablesTest.php @@ -0,0 +1,79 @@ +<?php + +namespace Tests\Icinga\Module\Director\CustomVariable; + +use Icinga\Module\Director\CustomVariable\CustomVariables; +use Icinga\Module\Director\Test\BaseTestCase; + +class CustomVariablesTest extends BaseTestCase +{ + protected $indent = ' '; + + public function testWhetherSpecialKeyNames() + { + $vars = $this->newVars(); + $vars->bla = 'da'; + $vars->{'aBc'} = 'normal'; + $vars->{'a-0'} = 'special'; + $expected = $this->indentVarsList([ + 'vars["a-0"] = "special"', + 'vars.aBc = "normal"', + 'vars.bla = "da"' + ]); + $this->assertEquals($expected, $vars->toConfigString()); + } + + public function testVarsCanBeUnsetAndSetAgain() + { + $vars = $this->newVars(); + $vars->one = 'two'; + unset($vars->one); + $vars->one = 'three'; + + $res = []; + foreach ($vars as $k => $v) { + $res[$k] = $v->getValue(); + } + + $this->assertEquals(['one' => 'three'], $res); + } + + public function testNumericKeysAreRenderedWithArraySyntax() + { + $vars = $this->newVars(); + $vars->{'1'} = 1; + $expected = $this->indentVarsList([ + 'vars["1"] = 1' + ]); + + $this->assertEquals( + $expected, + $vars->toConfigString(true) + ); + } + + public function testVariablesToExpression() + { + $vars = $this->newVars(); + $vars->bla = 'da'; + $vars->abc = '$val$'; + $expected = $this->indentVarsList([ + 'vars.abc = "$val$"', + 'vars.bla = "da"' + ]); + $this->assertEquals($expected, $vars->toConfigString(true)); + } + + protected function indentVarsList($vars) + { + return $this->indent . implode( + "\n" . $this->indent, + $vars + ) . "\n"; + } + + protected function newVars() + { + return new CustomVariables(); + } +} diff --git a/test/php/library/Director/Data/AssignFilterHelperTest.php b/test/php/library/Director/Data/AssignFilterHelperTest.php new file mode 100644 index 0000000..5fcdd95 --- /dev/null +++ b/test/php/library/Director/Data/AssignFilterHelperTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Data\Filter\Filter; +use Icinga\Module\Director\Data\AssignFilterHelper; +use Icinga\Module\Director\Objects\HostApplyMatches; +use Icinga\Module\Director\Test\BaseTestCase; + +class AssignFilterHelperTest extends BaseTestCase +{ + protected static $exampleHost; + + public static function setUpBeforeClass() + { + self::$exampleHost = (object) [ + 'address' => '127.0.0.1', + 'vars.operatingsystem' => 'centos', + 'vars.customer' => 'ACME', + 'vars.roles' => ['webserver', 'mailserver'], + 'vars.bool_string' => 'true', + 'groups' => ['web-server', 'mail-server'], + ]; + } + + public function testSimpleApplyFilter() + { + $this->assertFilterOutcome(true, 'host.address=true', self::$exampleHost); + $this->assertFilterOutcome(false, 'host.address=false', self::$exampleHost); + $this->assertFilterOutcome(true, 'host.address=false', (object) ['address' => null]); + $this->assertFilterOutcome(false, 'host.address=true', (object) ['address' => null]); + $this->assertFilterOutcome(true, 'host.address=%22127.0.0.%2A%22', self::$exampleHost); + } + + public function testListApplyFilter() + { + $this->assertFilterOutcome(true, 'host.vars.roles=%22*server%22', self::$exampleHost); + $this->assertFilterOutcome(true, 'host.groups=%22*-server%22', self::$exampleHost); + $this->assertFilterOutcome(false, 'host.groups=%22*-nothing%22', self::$exampleHost); + } + + public function testComplexApplyFilter() + { + $this->assertFilterOutcome( + true, + 'host.vars.operatingsystem=%5B%22centos%22%2C%22fedora%22%5D|host.vars.osfamily=%22redhat%22', + self::$exampleHost + ); + + $this->assertFilterOutcome( + false, + 'host.vars.operatingsystem=%5B%22centos%22%2C%22fedora%22%5D&(!(host.vars.customer=%22acme*%22))', + self::$exampleHost + ); + + $this->assertFilterOutcome( + true, + '!(host.vars.bool_string="false")&host.vars.operatingsystem="centos"', + self::$exampleHost + ); + } + + /** + * @param bool $expected + * @param string $filterQuery + * @param object $object + * @param string $message + */ + protected function assertFilterOutcome($expected, $filterQuery, $object, $message = null, $type = 'host') + { + $filter = Filter::fromQueryString($filterQuery); + + if ($type === 'host') { + HostApplyMatches::fixFilterColumns($filter); + } + + $helper = new AssignFilterHelper($filter); + $actual = $helper->matches($object); + + if ($message === null) { + $message = sprintf('with filter "%s"', $filterQuery); + } + + $this->assertEquals($expected, $actual, $message); + } +} diff --git a/test/php/library/Director/Data/RecursiveUtf8ValidatorTest.php b/test/php/library/Director/Data/RecursiveUtf8ValidatorTest.php new file mode 100644 index 0000000..1434d4a --- /dev/null +++ b/test/php/library/Director/Data/RecursiveUtf8ValidatorTest.php @@ -0,0 +1,45 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Module\Director\Data\RecursiveUtf8Validator; +use Icinga\Module\Director\Test\BaseTestCase; + +class RecursiveUtf8ValidatorTest extends BaseTestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testDetectInvalidUtf8Character() + { + RecursiveUtf8Validator::validateRows([ + (object) [ + 'name' => 'test 1', + 'value' => 'something', + ], + (object) [ + 'name' => 'test 2', + 'value' => "some\xa1\xa2thing", + ], + ]); + } + + public function testAcceptValidUtf8Characters() + { + $this->assertTrue(RecursiveUtf8Validator::validateRows([ + (object) [ + 'name' => 'test 1', + 'value' => "Some 🍻", + ], + (object) [ + 'name' => 'test 2', + 'value' => [ + (object) [ + 'its' => true, + ['💩'] + ] + ], + ], + ])); + } +} diff --git a/test/php/library/Director/IcingaConfig/AssignRendererTest.php b/test/php/library/Director/IcingaConfig/AssignRendererTest.php new file mode 100644 index 0000000..b9f574e --- /dev/null +++ b/test/php/library/Director/IcingaConfig/AssignRendererTest.php @@ -0,0 +1,126 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Data\Filter\Filter; +use Icinga\Module\Director\IcingaConfig\AssignRenderer; +use Icinga\Module\Director\Test\BaseTestCase; + +class AssignRendererTest extends BaseTestCase +{ + public function testEqualMatchIsCorrectlyRendered() + { + $string = 'host.name="localhost"'; + $expected = 'assign where host.name == "localhost"'; + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testNegationIsRenderedCorrectlyOnRootLevel() + { + $string = '!(host.name="one"&host.name="two")'; + $expected = 'assign where !(host.name == "one" && host.name == "two")'; + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testNegationIsRenderedCorrectlyOnDeeperLevel() + { + $string = 'host.address="127.*"&!host.name="localhost"'; + $expected = 'assign where match("127.*", host.address) && !(host.name == "localhost")'; + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testWildcardsRenderAMatchMethod() + { + $string = 'host.address="127.0.0.*"'; + $expected = 'assign where match("127.0.0.*", host.address)'; + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testACombinedFilterRendersCorrectly() + { + $string = 'host.name="*internal"|(service.vars.priority<2' + . '&host.vars.is_clustered=true)'; + + $expected = 'assign where match("*internal", host.name) ||' + . ' (service.vars.priority < 2 && host.vars.is_clustered)'; + + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testSlashesAreNotEscaped() + { + $string = 'host.name=' . json_encode('a/b'); + + $expected = 'assign where host.name == "a/b"'; + + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testFakeContainsOperatorRendersCorrectly() + { + $string = json_encode('member') . '=host.groups'; + + $expected = 'assign where "member" in host.groups'; + + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + + $string = json_encode('member') . '=host.vars.some_array'; + + $expected = 'assign where "member" in host.vars.some_array'; + + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testInArrayIsRenderedCorrectly() + { + $string = 'host.name=' . json_encode(array('a' ,'b')); + + $expected = 'assign where host.name in [ "a", "b" ]'; + + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + public function testWhetherSlashesAreNotEscaped() + { + $string = 'host.name=' . json_encode('a/b'); + + $expected = 'assign where host.name == "a/b"'; + + $this->assertEquals( + $expected, + $this->renderer($string)->renderAssign() + ); + } + + protected function renderer($string) + { + return AssignRenderer::forFilter(Filter::fromQueryString($string)); + } +} diff --git a/test/php/library/Director/IcingaConfig/ExtensibleSetTest.php b/test/php/library/Director/IcingaConfig/ExtensibleSetTest.php new file mode 100644 index 0000000..34bd83a --- /dev/null +++ b/test/php/library/Director/IcingaConfig/ExtensibleSetTest.php @@ -0,0 +1,162 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Module\Director\IcingaConfig\ExtensibleSet; +use Icinga\Module\Director\Objects\IcingaUser; +use Icinga\Module\Director\Test\BaseTestCase; + +class ExtensibleSetTest extends BaseTestCase +{ + public function testNoValuesResultInEmptySet() + { + $set = new ExtensibleSet(); + + $this->assertEquals( + array(), + $set->getResolvedValues() + ); + } + + public function testValuesPassedToConstructorAreAccepted() + { + $values = array('Val1', 'Val2', 'Val4'); + $set = new ExtensibleSet($values); + + $this->assertEquals( + $values, + $set->getResolvedValues() + ); + } + + public function testConstructorAcceptsSingleValues() + { + $set = new ExtensibleSet('Bla'); + + $this->assertEquals( + array('Bla'), + $set->getResolvedValues() + ); + } + + public function testSingleValuesCanBeBlacklisted() + { + $values = array('Val1', 'Val2', 'Val4'); + $set = new ExtensibleSet($values); + $set->blacklist('Val2'); + + $this->assertEquals( + array('Val1', 'Val4'), + $set->getResolvedValues() + ); + } + + public function testMultipleValuesCanBeBlacklisted() + { + $values = array('Val1', 'Val2', 'Val4'); + $set = new ExtensibleSet($values); + $set->blacklist(array('Val4', 'Val1')); + + $this->assertEquals( + array('Val2'), + $set->getResolvedValues() + ); + } + + public function testSimpleInheritanceWorksFine() + { + $values = array('Val1', 'Val2', 'Val4'); + $parent = new ExtensibleSet($values); + $child = new ExtensibleSet(); + $child->inheritFrom($parent); + + $this->assertEquals( + $values, + $child->getResolvedValues() + ); + } + + public function testWeCanInheritFromMultipleParents() + { + $p1set = array('p1a', 'p1c'); + $p2set = array('p2a', 'p2d'); + $parent1 = new ExtensibleSet($p1set); + $parent2 = new ExtensibleSet($p2set); + $child = new ExtensibleSet(); + $child->inheritFrom($parent1)->inheritFrom($parent2); + + $this->assertEquals( + $p2set, + $child->getResolvedValues() + ); + } + + public function testOwnValuesOverrideParents() + { + $cset = array('p1a', 'p1c'); + $pset = array('p2a', 'p2d'); + $child = new ExtensibleSet($cset); + $parent = new ExtensibleSet($pset); + $child->inheritFrom($parent); + + $this->assertEquals( + $cset, + $child->getResolvedValues() + ); + } + + public function testInheritedValuesCanBeBlacklisted() + { + $child = new ExtensibleSet(); + $child->blacklist('p2'); + + $pset = array('p1', 'p2', 'p3'); + $parent = new ExtensibleSet($pset); + + $child->inheritFrom($parent); + $child->blacklist(array('not', 'yet', 'p1')); + + $this->assertEquals( + array('p3'), + $child->getResolvedValues() + ); + + $child->blacklist(array('p3')); + $this->assertEquals( + array(), + $child->getResolvedValues() + ); + } + + public function testInheritedValuesCanBeExtended() + { + $pset = array('p1', 'p2', 'p3'); + + $child = new ExtensibleSet(); + $child->extend('p5'); + + $parent = new ExtensibleSet($pset); + $child->inheritFrom($parent); + + $this->assertEquals( + array('p1', 'p2', 'p3', 'p5'), + $child->getResolvedValues() + ); + } + + public function testCombinedDefinitionRendersCorrectly() + { + $set = new ExtensibleSet(array('Pre', 'Def', 'Ined')); + $set->blacklist(array('And', 'Not', 'Those')); + $set->extend('PlusThis'); + + $out = ' key_name = [ Pre, Def, Ined ]' . "\n" + . ' key_name += [ PlusThis ]' . "\n" + . ' key_name -= [ And, Not, Those ]' . "\n"; + + $this->assertEquals( + $out, + $set->renderAs('key_name') + ); + } +} diff --git a/test/php/library/Director/IcingaConfig/IcingaConfigHelperTest.php b/test/php/library/Director/IcingaConfig/IcingaConfigHelperTest.php new file mode 100644 index 0000000..506f3b8 --- /dev/null +++ b/test/php/library/Director/IcingaConfig/IcingaConfigHelperTest.php @@ -0,0 +1,130 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c; +use Icinga\Module\Director\Test\BaseTestCase; + +class IcingaConfigHelperTest extends BaseTestCase +{ + public function testWhetherIntervalStringIsCorrectlyParsed() + { + $this->assertEquals(c::parseInterval('0'), 0); + $this->assertEquals(c::parseInterval('0s'), 0); + $this->assertEquals(c::parseInterval('10'), 10); + $this->assertEquals(c::parseInterval('70s'), 70); + $this->assertEquals(c::parseInterval('5m 10s'), 310); + $this->assertEquals(c::parseInterval('5m 60s'), 360); + $this->assertEquals(c::parseInterval('1h 5m 60s'), 3960); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testWhetherInvalidIntervalStringRaisesException() + { + c::parseInterval('1h 5m 60x'); + } + + public function testWhetherAnEmptyValueGivesNull() + { + $this->assertNull(c::parseInterval('')); + $this->assertNull(c::parseInterval(null)); + } + + public function testWhetherIntervalStringIsCorrectlyRendered() + { + $this->assertEquals(c::renderInterval(10), '10s'); + $this->assertEquals(c::renderInterval(60), '1m'); + $this->assertEquals(c::renderInterval(121), '121s'); + $this->assertEquals(c::renderInterval(3600), '1h'); + $this->assertEquals(c::renderInterval(86400), '1d'); + $this->assertEquals(c::renderInterval(86459), '86459s'); + } + + public function testCorrectlyIdentifiesReservedWords() + { + $this->assertTrue(c::isReserved('include'), 'include is a reserved word'); + $this->assertFalse(c::isReserved(0), '(int) 0 is not a reserved word'); + $this->assertFalse(c::isReserved(1), '(int) 1 is not a reserved word'); + $this->assertFalse(c::isReserved(true), '(boolean) true is not a reserved word'); + $this->assertTrue(c::isReserved('true'), '(string) true is a reserved word'); + } + + public function testWhetherDictionaryRendersCorrectly() + { + $dict = (object) [ + 'key1' => 'bla', + 'include' => 'reserved', + 'spe cial' => 'value', + '0' => 'numeric', + ]; + $this->assertEquals( + c::renderDictionary($dict), + rtrim($this->loadRendered('dict1')) + ); + } + + protected function loadRendered($name) + { + return file_get_contents(__DIR__ . '/rendered/' . $name . '.out'); + } + + public function testRenderStringIsCorrectlyRendered() + { + $this->assertEquals(c::renderString('val1\\\val2'), '"val1\\\\\\\\val2"'); + $this->assertEquals(c::renderString('"val1"'), '"\"val1\""'); + $this->assertEquals(c::renderString('\$val\$'), '"\\\\$val\\\\$"'); + $this->assertEquals(c::renderString('\t'), '"\\\\t"'); + $this->assertEquals(c::renderString('\r'), '"\\\\r"'); + $this->assertEquals(c::renderString('\n'), '"\\\\n"'); + $this->assertEquals(c::renderString('\f'), '"\\\\f"'); + } + + public function testMacrosAreDetected() + { + $this->assertFalse(c::stringHasMacro('$$vars$')); + $this->assertFalse(c::stringHasMacro('$$')); + $this->assertTrue(c::stringHasMacro('$vars$$')); + $this->assertTrue(c::stringHasMacro('$multiple$$vars.nested.name$$vars$ is here')); + $this->assertTrue(c::stringHasMacro('some $vars.nested.name$ is here')); + $this->assertTrue(c::stringHasMacro('some $vars.nested.name$$vars.even.more$')); + $this->assertTrue(c::stringHasMacro('$vars.nested.name$$a$$$$not$')); + $this->assertTrue(c::stringHasMacro('MSSQL$$$config$')); + $this->assertTrue(c::stringHasMacro('MSSQL$$$config$', 'config')); + $this->assertTrue(c::stringHasMacro('MSSQL$$$nix$ and $config$', 'config')); + $this->assertFalse(c::stringHasMacro('MSSQL$$$nix$config$ and $$', 'config')); + $this->assertFalse(c::stringHasMacro('MSSQL$$$nix$ and $$config$', 'config')); + $this->assertFalse(c::stringHasMacro('MSSQL$$$config$', 'conf')); + } + + public function testRenderStringWithVariables() + { + $this->assertEquals('"Before " + var', c::renderStringWithVariables('Before $var$')); + $this->assertEquals(c::renderStringWithVariables('$var$ After'), 'var + " After"'); + $this->assertEquals(c::renderStringWithVariables('$var$'), 'var'); + $this->assertEquals(c::renderStringWithVariables('$$var$$'), '"$$var$$"'); + $this->assertEquals(c::renderStringWithVariables('Before $$var$$ After'), '"Before $$var$$ After"'); + $this->assertEquals( + '"Before " + name1 + " " + name2 + " After"', + c::renderStringWithVariables('Before $name1$ $name2$ After') + ); + } + + public function testRenderStringWithVariablesX() + { + $this->assertEquals( + '"Before " + var1 + " " + var2 + " After"', + c::renderStringWithVariables('Before $var1$ $var2$ After') + ); + $this->assertEquals( + 'host.vars.custom', + c::renderStringWithVariables('$host.vars.custom$') + ); + $this->assertEquals('"$var\"$"', c::renderStringWithVariables('$var"$')); + $this->assertEquals( + '"\\\\tI am\\\\rrendering\\\\nproperly\\\\fand I " + support + " \"multiple\" " + variables + "\\\\$"', + c::renderStringWithVariables('\tI am\rrendering\nproperly\fand I $support$ "multiple" $variables$\$') + ); + } +} diff --git a/test/php/library/Director/IcingaConfig/StateFilterTest.php b/test/php/library/Director/IcingaConfig/StateFilterTest.php new file mode 100644 index 0000000..82e94d8 --- /dev/null +++ b/test/php/library/Director/IcingaConfig/StateFilterTest.php @@ -0,0 +1,171 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Module\Director\IcingaConfig\StateFilterSet; +use Icinga\Module\Director\Objects\IcingaUser; +use Icinga\Module\Director\Test\BaseTestCase; + +class StateFilterSetTest extends BaseTestCase +{ + protected $testUserName1 = '__testuser2'; + + protected $testUserName2 = '__testuser2'; + + public function testIsEmptyForAnUnstoredUser() + { + $this->assertEquals( + array(), + StateFilterSet::forIcingaObject( + IcingaUser::create(), + 'states' + )->getResolvedValues() + ); + } + + /** + * @expectedException \Icinga\Exception\InvalidPropertyException + */ + public function testFailsForInvalidProperties() + { + $set = new StateFilterSet('bla'); + } + + /** + * @expectedException \Icinga\Exception\ProgrammingError + */ + public function testCannotBeStoredForAnUnstoredUser() + { + StateFilterSet::forIcingaObject( + $this->user1(), + 'states' + )->override( + array('OK', 'Down') + )->store(); + } + + public function testCanBeStored() + { + if ($this->skipForMissingDb()) { + return; + } + + $states = $this->simpleUnstoredSetForStoredUser(); + + $this->assertTrue($states->store()); + $states->getObject()->delete(); + } + + public function testWillNotBeStoredTwice() + { + if ($this->skipForMissingDb()) { + return; + } + + $states = $this->simpleUnstoredSetForStoredUser(); + + $this->assertTrue($states->store()); + $this->assertFalse($states->store()); + $this->assertFalse($states->store()); + $states->getObject()->delete(); + } + + public function testComplexDefinitionsCanBeStored() + { + if ($this->skipForMissingDb()) { + return; + } + + $states = $this->complexUnstoredSetForStoredUser(); + + $this->assertTrue($states->store()); + $states->getObject()->delete(); + } + + public function testComplexDefinitionsCanBeLoadedAndRenderCorrectly() + { + if ($this->skipForMissingDb()) { + return; + } + + $states = $this->complexUnstoredSetForStoredUser(); + $user = $states->getObject(); + + $this->assertTrue($states->store()); + + $states = StateFilterSet::forIcingaObject($user, 'states'); + $expected = ' states = [ Down, OK, Up ]' . "\n" + . ' states += [ Warning ]' . "\n" + . ' states -= [ Up ]' . "\n"; + + $this->assertEquals( + $expected, + $states->renderAs('states') + ); + + $states->getObject()->delete(); + } + + protected function simpleUnstoredSetForStoredUser() + { + $user = $this->user1(); + $user->store($this->getDb()); + + $states = StateFilterSet::forIcingaObject( + $user, + 'states' + )->override( + array('OK', 'Down') + ); + + return $states; + } + + protected function complexUnstoredSetForStoredUser() + { + $user = $this->user2(); + $user->store($this->getDb()); + + $states = StateFilterSet::forIcingaObject( + $user, + 'states' + )->override( + array('OK', 'Down', 'Up') + )->blacklist('Up')->extend('Warning'); + + return $states; + } + + protected function user1() + { + return IcingaUser::create(array( + 'object_type' => 'object', + 'object_name' => $this->testUserName1 + )); + } + + protected function user2() + { + return IcingaUser::create(array( + 'object_type' => 'object', + 'object_name' => $this->testUserName2 + )); + } + + public function tearDown() + { + if ($this->hasDb()) { + $users = array( + $this->testUserName1, + $this->testUserName2 + ); + + $db = $this->getDb(); + foreach ($users as $user) { + if (IcingaUser::exists($user, $db)) { + IcingaUser::load($user, $db)->delete(); + } + } + } + } +} diff --git a/test/php/library/Director/IcingaConfig/rendered/dict1.out b/test/php/library/Director/IcingaConfig/rendered/dict1.out new file mode 100644 index 0000000..9f4e6bf --- /dev/null +++ b/test/php/library/Director/IcingaConfig/rendered/dict1.out @@ -0,0 +1,6 @@ +{ + "0" = numeric + @include = reserved + key1 = bla + "spe cial" = value +} diff --git a/test/php/library/Director/Import/HostSyncTest.php b/test/php/library/Director/Import/HostSyncTest.php new file mode 100644 index 0000000..eeee7a4 --- /dev/null +++ b/test/php/library/Director/Import/HostSyncTest.php @@ -0,0 +1,250 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Module\Director\Objects\IcingaHostGroup; +use Icinga\Module\Director\Objects\IcingaObject; +use Icinga\Module\Director\Test\SyncTest; + +class HostSyncTest extends SyncTest +{ + protected $objectType = 'host'; + + protected $keyColumn = 'host'; + + public function testSimpleSync() + { + $this->runImport(array( + array( + 'host' => 'SYNCTEST_simple', + 'address' => '127.0.0.1', + 'os' => 'Linux' + ) + )); + + $this->setUpProperty(array( + 'source_expression' => '${host}', + 'destination_field' => 'object_name', + 'priority' => 10, + )); + $this->setUpProperty(array( + 'source_expression' => '${address}', + 'destination_field' => 'address', + 'priority' => 11, + )); + $this->setUpProperty(array( + 'source_expression' => '${os}', + 'destination_field' => 'vars.os', + 'priority' => 12, + )); + + $this->assertTrue($this->sync->hasModifications(), 'Should have modifications pending'); + $this->assertGreaterThan(0, $this->sync->apply(), 'Should successfully apply at least 1 update'); + $this->assertFalse($this->sync->hasModifications(), 'Should not have modifications pending after sync'); + } + + public function testSyncWithoutData() + { + $this->runImport(array()); + + $this->setUpProperty(array( + 'source_expression' => '${host}', + 'destination_field' => 'object_name', + 'priority' => 10, + )); + + $this->assertFalse($this->sync->hasModifications(), 'Should not have modifications pending'); + } + + public function testSyncMultipleGroups() + { + $this->requireGroups(['SYNCTEST_groupa', 'SYNCTEST_groupb']); + $this->runImport([[ + 'host' => 'SYNCTEST_groups1', + 'g1' => 'SYNCTEST_groupa', + 'g2' => 'SYNCTEST_groupb' + ], [ + 'host' => 'SYNCTEST_groups2', + 'g1' => null, + 'g2' => 'SYNCTEST_groupb' + ]]); + + $this->setUpProperty(array( + 'source_expression' => '${host}', + 'destination_field' => 'object_name', + 'priority' => 10, + )); + $this->setUpProperty(array( + 'source_expression' => '${g1}', + 'destination_field' => 'groups', + 'priority' => 12, + )); + $this->setUpProperty(array( + 'source_expression' => '${g2}', + 'destination_field' => 'groups', + 'priority' => 11, + )); + + $this->assertTrue($this->sync->hasModifications(), 'Should have modifications pending'); + + $modifications = array(); + /** @var IcingaObject $mod */ + foreach ($this->sync->getExpectedModifications() as $mod) { + $name = $mod->object_name; + $modifications[$name] = $mod; + + switch ($name) { + case 'SYNCTEST_groups1': + $this->assertEquals([ + 'SYNCTEST_groupa', + 'SYNCTEST_groupb', + ], $mod->get('groups')); + break; + case 'SYNCTEST_groups2': + $this->assertEquals([ + 'SYNCTEST_groupb', + ], $mod->get('groups')); + break; + } + } + + $this->assertGreaterThan(0, $this->sync->apply(), 'Should successfully apply at least 1 update'); + $this->assertFalse($this->sync->hasModifications(), 'Should not have modifications pending after sync'); + } + + public function testSyncFilteredData() + { + $this->runImport(array( + array( + 'host' => 'SYNCTEST_filtered_in', + 'address' => '127.0.0.1', + 'os' => 'Linux', + 'sync' => 'yes' + ), + array( + 'host' => 'SYNCTEST_filtered_out', + 'address' => '127.0.0.1', + 'os' => null, + 'sync' => 'no' + ), + array( + 'host' => 'SYNCTEST_filtered_in_unusedfield', + 'address' => '127.0.0.1', + 'os' => null, + 'sync' => 'no', + 'othersync' => '1' + ), + array( + 'host' => 'SYNCTEST_filtered_in_unusedfield_propfilter', + 'address' => '127.0.0.1', + 'os' => null, + 'magic' => '2', + 'sync' => 'no', + 'othersync' => '1' + ) + )); + + $this->rule->set('filter_expression', 'sync=yes|othersync=1'); + $this->rule->store(); + + $this->setUpProperty(array( + 'source_expression' => '${host}', + 'destination_field' => 'object_name', + 'priority' => 10, + )); + $this->setUpProperty(array( + 'source_expression' => '${address}', + 'destination_field' => 'address', + 'priority' => 11, + )); + $this->setUpProperty(array( + 'source_expression' => 'test', + 'destination_field' => 'vars.magic', + 'filter_expression' => 'magic!=', + 'priority' => 12, + )); + + $modifications = array(); + /** @var IcingaObject $mod */ + foreach ($this->sync->getExpectedModifications() as $mod) { + $name = $mod->object_name; + $modifications[$name] = $mod; + + $this->assertEquals( + '127.0.0.1', + $mod->get('address'), + $name . ': address should not be synced' + ); + $this->assertNull($mod->get('vars.os'), $name . ': vars.os should not be synced'); + + if ($name === 'SYNCTEST_filtered_in_unusedfield_propfilter') { + $this->assertEquals( + 'test', + $mod->get('vars.magic'), + $name . ': vars.magic should not be synced' + ); + } else { + $this->assertNull($mod->get('vars.magic'), $name . ': vars.magic should not be synced'); + } + } + + $this->assertArrayHasKey( + 'SYNCTEST_filtered_in', + $modifications, + 'SYNCTEST_filtered_in should be modified' + ); + $this->assertArrayNotHasKey( + 'SYNCTEST_filtered_out', + $modifications, + 'SYNCTEST_filtered_out should be synced' + ); + $this->assertArrayHasKey( + 'SYNCTEST_filtered_in_unusedfield', + $modifications, + 'SYNCTEST_filtered_in_unusedfield should be modified' + ); + $this->assertArrayHasKey( + 'SYNCTEST_filtered_in_unusedfield_propfilter', + $modifications, + 'SYNCTEST_filtered_in_unusedfield_propfilter should be modified' + ); + } + + /** + * @param $names + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\IcingaException + */ + protected function requireGroups($names) + { + foreach ($names as $name) { + if (! IcingaHostGroup::exists($name, $this->getDb())) { + IcingaHostGroup::create([ + 'object_name' => $name, + 'object_type' => 'object', + ], $this->getDb())->store(); + } + } + } + + /** + * @param $names + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\IcingaException + * @throws \Icinga\Exception\NotFoundError + */ + protected function removeGroups($names) + { + foreach ($names as $name) { + if (IcingaHostGroup::exists($name, $this->getDb())) { + IcingaHostGroup::load($name, $this->getDb())->delete(); + } + } + } + + public function tearDown() + { + $this->removeGroups(['SYNCTEST_groupa', 'SYNCTEST_groupb']); + parent::tearDown(); + } +} diff --git a/test/php/library/Director/Import/ImportSourceRestApiTest.php b/test/php/library/Director/Import/ImportSourceRestApiTest.php new file mode 100644 index 0000000..7bbce2d --- /dev/null +++ b/test/php/library/Director/Import/ImportSourceRestApiTest.php @@ -0,0 +1,29 @@ +<?php + +namespace Tests\Icinga\Module\Director\Import; + +use Icinga\Module\Director\Import\ImportSourceRestApi; +use Icinga\Module\Director\Test\BaseTestCase; + +class ImportSourceRestApiTest extends BaseTestCase +{ + public function testExtractProperty() + { + $examples = [ + '' => json_decode('[{"name":"blau"}]'), + 'objects' => json_decode('{"objects":[{"name":"blau"}]}'), + 'results.objects.all' => json_decode('{"results":{"objects":{"all":[{"name":"blau"}]}}}'), + 'results\.objects.all' => json_decode('{"results.objects":{"all":[{"name":"blau"}]}}'), + ]; + + $source = new ImportSourceRestApi(); + + foreach ($examples as $property => $data) { + $source->setSettings(['extract_property' => $property]); + $result = static::callMethod($source, 'extractProperty', [$data]); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('name', (array) $result[0]); + } + } +} diff --git a/test/php/library/Director/Import/SyncUtilsTest.php b/test/php/library/Director/Import/SyncUtilsTest.php new file mode 100644 index 0000000..ff40856 --- /dev/null +++ b/test/php/library/Director/Import/SyncUtilsTest.php @@ -0,0 +1,108 @@ +<?php + +namespace Tests\Icinga\Module\Director\IcingaConfig; + +use Icinga\Module\Director\Import\SyncUtils; +use Icinga\Module\Director\Test\BaseTestCase; + +class SyncUtilsTest extends BaseTestCase +{ + public function testVariableNamesAreExtracted() + { + $this->assertEquals( + array( + 'var.name', + '$Special Var' + ), + SyncUtils::extractVariableNames('This ${var.name} is ${$Special Var} are vars') + ); + + $this->assertEquals( + array(), + SyncUtils::extractVariableNames('No ${var.name vars ${$Special Var here') + ); + } + + public function testSpecificValuesCanBeRetrievedByName() + { + $row = (object)array( + 'host' => 'localhost', + 'ipaddress' => '127.0.0.1' + ); + + $this->assertEquals( + '127.0.0.1', + SyncUtils::getSpecificValue($row, 'ipaddress') + ); + } + + public function testMissingPropertiesMustBeNull() + { + $row = (object)array( + 'host' => 'localhost', + 'ipaddress' => '127.0.0.1' + ); + + $this->assertNull( + SyncUtils::getSpecificValue($row, 'address') + ); + } + + public function testNestedValuesCanBeRetrievedByPath() + { + $row = $this->getSampleRow(); + + $this->assertEquals( + '192.0.2.10', + SyncUtils::getSpecificValue($row, 'addresses.entries.eth0:1') + ); + + $this->assertEquals( + 2, + SyncUtils::getSpecificValue($row, 'addresses.count') + ); + } + + public function testRootVariablesCanBeExtracted() + { + $vars = array('test', 'nested.test', 'nested.dee.per'); + $this->assertEquals( + array( + 'test' => 'test', + 'nested' => 'nested' + ), + SyncUtils::getRootVariables($vars) + ); + } + + public function testMultipleVariablesAreBeingReplacedCorrectly() + { + $string = '${addresses.entries.lo} and ${addresses.entries.eth0:1} are' + . ' ${This one?.$höüld be}${addressesmissing}'; + + $this->assertEquals( + '127.0.0.1 and 192.0.2.10 are fine', + SyncUtils::fillVariables( + $string, + $this->getSampleRow() + ) + ); + } + + protected function getSampleRow() + { + return (object) array( + 'host' => 'localhost', + 'addresses' => (object) array( + 'count' => 2, + 'entries' => (object) array( + 'lo' => '127.0.0.1', + 'eth0:1' => '192.0.2.10', + ) + ), + 'This one?' => (object) array( + '$höüld be' => 'fine' + ) + ); + } +} diff --git a/test/php/library/Director/Objects/HostApplyMatchesTest.php b/test/php/library/Director/Objects/HostApplyMatchesTest.php new file mode 100644 index 0000000..b9f22ca --- /dev/null +++ b/test/php/library/Director/Objects/HostApplyMatchesTest.php @@ -0,0 +1,93 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Data\Filter\Filter; +use Icinga\Module\Director\Objects\HostApplyMatches; +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Test\BaseTestCase; + +class HostApplyMatchesTest extends BaseTestCase +{ + public function testExactMatches() + { + $matcher = HostApplyMatches::prepare($this->sampleHost()); + $this->assertTrue( + $matcher->matchesFilter( + Filter::fromQueryString('host.name=%22aha%22') + ) + ); + $this->assertFalse( + $matcher->matchesFilter( + Filter::fromQueryString('host.name=%22ahaa%22') + ) + ); + } + + public function testWildcardMatches() + { + $matcher = HostApplyMatches::prepare($this->sampleHost()); + $this->assertTrue( + $matcher->matchesFilter( + Filter::fromQueryString('host.name=%22ah*%22') + ) + ); + $this->assertTrue( + $matcher->matchesFilter( + Filter::fromQueryString('host.name=%22*h*%22') + ) + ); + $this->assertFalse( + $matcher->matchesFilter( + Filter::fromQueryString('host.name=%22*g*%22') + ) + ); + } + + public function testStringVariableMatches() + { + $matcher = HostApplyMatches::prepare($this->sampleHost()); + $this->assertTrue( + $matcher->matchesFilter( + Filter::fromQueryString('host.vars.location=%22*urem*%22') + ) + ); + $this->assertTrue( + $matcher->matchesFilter( + Filter::fromQueryString('host.vars.location=%22Nuremberg%22') + ) + ); + $this->assertFalse( + $matcher->matchesFilter( + Filter::fromQueryString('host.vars.location=%22Nurembergg%22') + ) + ); + } + + public function testArrayVariableMatches() + { + $matcher = HostApplyMatches::prepare($this->sampleHost()); + $this->assertTrue( + $matcher->matchesFilter( + Filter::fromQueryString('%22Amazing%22=host.vars.tags') + ) + ); + $this->assertFalse( + $matcher->matchesFilter( + Filter::fromQueryString('%22Amazingg%22=host.vars.tags') + ) + ); + } + + protected function sampleHost() + { + return IcingaHost::create(array( + 'object_type' => 'object', + 'object_name' => 'aha', + 'vars' => array( + 'location' => 'Nuremberg', + 'tags' => array('Special', 'Amazing'), + ) + ), $this->getDb()); + } +} diff --git a/test/php/library/Director/Objects/HostGroupMembershipResolverTest.php b/test/php/library/Director/Objects/HostGroupMembershipResolverTest.php new file mode 100644 index 0000000..cf2fb36 --- /dev/null +++ b/test/php/library/Director/Objects/HostGroupMembershipResolverTest.php @@ -0,0 +1,353 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; +use Icinga\Module\Director\Exception\DuplicateKeyException; +use Icinga\Module\Director\Objects\HostGroupMembershipResolver; +use Icinga\Module\Director\Objects\IcingaObject; +use Icinga\Module\Director\Repository\IcingaTemplateRepository; +use Icinga\Module\Director\Test\BaseTestCase; + +class HostGroupMembershipResolverTest extends BaseTestCase +{ + const PREFIX = '__groupmembership'; + const TYPE = 'host'; + + public function setUp() + { + IcingaTemplateRepository::clear(); + } + + public static function cleanArtifacts() + { + $db = static::getDb()->getDbAdapter(); + + $where = sprintf("object_name LIKE '%s%%'", self::PREFIX); + + $db->delete('icinga_' . self::TYPE . 'group', $where); + + $db->delete('icinga_' . self::TYPE, $where . " AND object_type = 'object'"); + $db->delete('icinga_' . self::TYPE, $where); + } + + public static function setUpBeforeClass() + { + static::cleanArtifacts(); + } + + public static function tearDownAfterClass() + { + static::cleanArtifacts(); + } + + /** + * @param string $type + * @param string $name + * @param array $props + * + * @return IcingaObject + * @throws DuplicateKeyException + * @throws \Icinga\Exception\ConfigurationError + */ + protected function object($type, $name, $props = []) + { + $db = $this->getDb(); + $fullName = self::PREFIX . $name; + $object = null; + + try { + $object = IcingaObject::loadByType($type, $fullName, $db); + + foreach ($props as $k => $v) { + $object->set($k, $v); + } + + $object->store(); + } catch (NotFoundError $e) { + $object = null; + } + + if ($object === null) { + $object = IcingaObject::createByType($type, array_merge([ + 'object_name' => $fullName, + 'object_type' => 'object', + ], $props), $this->getDb()); + + $object->store(); + } + + return $object; + } + + protected function objects($type) + { + $dummy = DbObjectTypeRegistry::newObject($type); + + $table = $dummy->getTableName(); + $query = $this->getDb()->getDbAdapter()->select() + ->from($table) + ->where('object_name LIKE ?', self::PREFIX . '%'); + + $objects = []; + $l = strlen(self::PREFIX); + + foreach ($dummy::loadAll($this->getDb(), $query) as $object) { + $key = substr($object->getObjectName(), $l); + $objects[$key] = $object; + } + + return $objects; + } + + protected function resolved() + { + $db = $this->getDb()->getDbAdapter(); + + $select = $db->select() + ->from( + ['r' => 'icinga_' . self::TYPE . 'group_' . self::TYPE . '_resolved'], + [] + )->join( + ['o' => 'icinga_' . self::TYPE], + 'o.id = r.' . self::TYPE . '_id', + ['object' => 'object_name'] + )->join( + ['g' => 'icinga_' . self::TYPE . 'group'], + 'g.id = r.' . self::TYPE . 'group_id', + ['groupname' => 'object_name'] + ); + + $map = []; + $l = strlen(self::PREFIX); + + foreach ($db->fetchAll($select) as $row) { + $o = $row->object; + $g = $row->groupname; + + if (! substr($o, 0, $l) === self::PREFIX) { + continue; + } + $o = substr($o, $l); + + if (! substr($g, 0, $l) === self::PREFIX) { + continue; + } + $g = substr($g, $l); + + if (! array_key_exists($o, $map)) { + $map[$o] = []; + } + + $map[$o][] = $g; + } + + return $map; + } + + /** + * Creates: + * + * - 1 template + * - 10 hosts importing the template with a var match_var=magic + * + * @throws DuplicateKeyException + * @throws \Icinga\Exception\ConfigurationError + */ + public function testCreateHosts() + { + // template that sets a group later + $template = $this->object('host', 'template', [ + 'object_type' => 'template', + ]); + $this->assertTrue($template->hasBeenLoadedFromDb()); + + // hosts to assign groups + for ($i = 1; $i <= 10; $i++) { + $host = $this->object('host', $i, [ + 'imports' => self::PREFIX . 'template', + 'vars.match_var' => 'magic' + ]); + $this->assertTrue($host->hasBeenLoadedFromDb()); + } + } + + /** + * Creates: + * + * - 10 hostgroups applying on hosts with match_var=magic + * - 2 static hostgroups + * + * @throws DuplicateKeyException + * @throws \Icinga\Exception\ConfigurationError + */ + public function testCreateHostgroups() + { + $filter = 'host.vars.match_var=%22magic%22'; + for ($i = 1; $i <= 10; $i++) { + $hostgroup = $this->object('hostgroup', 'apply' . $i, [ + 'assign_filter' => $filter + ]); + $this->assertTrue($hostgroup->hasBeenLoadedFromDb()); + } + + // static groups + for ($i = 1; $i <= 2; $i++) { + $hostgroup = $this->object('hostgroup', 'static' . $i); + $this->assertTrue($hostgroup->hasBeenLoadedFromDb()); + } + } + + /** + * Assigns static groups to: + * + * - the template + * - the first host + * + * @throws DuplicateKeyException + * @throws \Icinga\Exception\ConfigurationError + * + * @depends testCreateHosts + * @depends testCreateHostgroups + */ + public function testAddStaticGroups() + { + // add group to template + $template = $this->object('host', 'template'); + $template->setGroups(self::PREFIX . 'static1'); + $template->store(); + $this->assertFalse($template->hasBeenModified()); + + // add group to first host + $host = $this->object('host', 1); + $host->setGroups(self::PREFIX . 'static2'); + $host->store(); + $this->assertFalse($host->hasBeenModified()); + } + + /** + * Asserts that static groups are resolved for hosts: + * + * - all but first should have static1 + * - first should have static2 + * + * @depends testAddStaticGroups + */ + public function testStaticResolvedMappings() + { + $resolved = $this->resolved(); + + $this->assertArrayHasKey( + 1, + $resolved, + 'Host 1 must have groups resolved' + ); + + $this->assertContains( + 'static2', + $resolved[1], + 'Host template must have static group 1' + ); + + $hosts = $this->objects('host'); + $this->assertNotEmpty($hosts, 'Must have hosts found in DB'); + + foreach ($hosts as $name => $host) { + if ($host->object_type === 'template') { + continue; + } + + $this->assertArrayHasKey( + $name, + $resolved, + 'All hosts must have groups resolved' + ); + + if ($name === 1) { + $this->assertNotContains( + 'static1', + $resolved[$name], + 'First host must not have static group 1' + ); + } else { + $this->assertContains( + 'static1', + $resolved[$name], + 'All hosts but the first must have static group 1' + ); + } + } + } + + /** + * @depends testCreateHostgroups + */ + public function testApplyResolvedMappings() + { + $resolved = $this->resolved(); + + $hosts = $this->objects('host'); + $this->assertNotEmpty($hosts, 'Must have hosts found in DB'); + + foreach ($hosts as $name => $host) { + if ($host->object_type === 'template') { + continue; + } + + $this->assertArrayHasKey($name, $resolved, 'Host must have groups resolved'); + + for ($i = 1; $i <= 10; $i++) { + $this->assertContains( + 'apply' . $i, + $resolved[$name], + 'All Host objects must have all applied groups' + ); + } + } + } + + /** + * @throws DuplicateKeyException + * @throws \Icinga\Exception\ConfigurationError + * + * @depends testAddStaticGroups + */ + public function testChangeAppliedGroupsAfterStatic() + { + $filter = 'host.vars.match_var=%22magic*%22'; + + $hostgroup = $this->object('hostgroup', 'apply1', [ + 'assign_filter' => $filter + ]); + $this->assertTrue($hostgroup->hasBeenLoadedFromDb()); + $this->assertFalse($hostgroup->hasBeenModified()); + + $resolved = $this->resolved(); + + for ($i = 1; $i <= 10; $i++) { + $this->assertContains( + 'apply1', + $resolved[$i], + 'All Host objects must have apply1 group' + ); + } + } + + /** + * @throws \Icinga\Exception\ConfigurationError + * @throws \Zend_Db_Adapter_Exception + * + * @depends testStaticResolvedMappings + * @depends testApplyResolvedMappings + * @depends testChangeAppliedGroupsAfterStatic + */ + public function testFullRecheck() + { + $resolver = new HostGroupMembershipResolver($this->getDb()); + + $resolver->checkDb(); + $this->assertEmpty($resolver->getNewMappings(), 'There should not be any new mappings'); + $this->assertEmpty($resolver->getOutdatedMappings(), 'There should not be any outdated mappings'); + } +} diff --git a/test/php/library/Director/Objects/IcingaCommandTest.php b/test/php/library/Director/Objects/IcingaCommandTest.php new file mode 100644 index 0000000..8e564d8 --- /dev/null +++ b/test/php/library/Director/Objects/IcingaCommandTest.php @@ -0,0 +1,216 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\Objects\IcingaCommand; +use Icinga\Module\Director\Test\BaseTestCase; + +class IcingaCommandTest extends BaseTestCase +{ + protected $testCommandName = '___TEST___command'; + + public function testCommandsWithArgumentsCanBeCreated() + { + $command = $this->command(); + $command->arguments = array( + '-H' => '$host$' + ); + + $this->assertEquals( + '-H', + $command->arguments()->get('-H')->argument_name + ); + + $this->assertEquals( + '$host$', + $command->toPlainObject()->arguments['-H'] + ); + + $command->arguments()->get('-H')->required = true; + } + + public function testCommandsWithArgumentsCanBeModified() + { + $command = $this->command(); + $command->arguments = array( + '-H' => '$host$' + ); + + $command->arguments = array( + '-x' => (object) array( + 'required' => true + ) + ); + + $this->assertEquals( + null, + $command->arguments()->get('-H') + ); + + $this->assertEquals( + 'y', + $command->arguments()->get('-x')->get('required') + ); + + $this->assertEquals( + true, + $command->toPlainObject()->arguments['-x']->required + ); + } + + public function testCanBePersistedToDb() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $command = $this->newCommandWithArguments(); + + $this->assertEquals( + $command->store($db), + true + ); + + + $command->delete(); + } + + public function testCanBeLoadedFromDb() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $name = $this->testCommandName; + $command = $this->newCommandWithArguments($db); + $command->store($db); + + $command = IcingaCommand::load($name, $db); + $this->assertEquals( + $command->object_name, + $name + ); + + $command->delete(); + } + + public function testArgumentMotificationsAreDetected() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $command = $this->newCommandWithArguments($db); + $command->store($db); + $command->arguments()->set('-H', 'no-host'); + $this->assertTrue($command->hasBeenModified()); + $this->assertTrue($command->store()); + $command->delete(); + } + + protected function newCommandWithArguments() + { + $command = $this->command(); + $command->arguments = array( + '-H' => '$host$', + '-x' => (object) array( + 'required' => true, + 'value' => 'bal' + ) + ); + + return $command; + } + + public function testAbsolutePathsAreDetected() + { + $command = $this->command(); + $command->command = 'C:\\test.exe'; + + $this->assertEquals( + $this->loadRendered('command1'), + (string) $command + ); + + $command->command = '/tmp/bla'; + + $this->assertEquals( + $this->loadRendered('command2'), + (string) $command + ); + + $command->command = 'tmp/bla'; + + $this->assertEquals( + $this->loadRendered('command3'), + (string) $command + ); + + $command->command = '\\\\network\\share\\bla.exe'; + + $this->assertEquals( + $this->loadRendered('command4'), + (string) $command + ); + + $command->command = 'BlahDir + \\network\\share\\bla.exe'; + + $this->assertEquals( + $this->loadRendered('command5'), + (string) $command + ); + + $command->command = 'network\\share\\bla.exe'; + + $this->assertEquals( + $this->loadRendered('command6'), + (string) $command + ); + } + + public function testSimpleSetIfIsRendered() + { + $command = $this->command(); + $command->command = 'bla'; + $command->arguments = array( + '-a' => (object) array( + 'set_if' => '$a$', + ) + ); + + $this->assertEquals( + $this->loadRendered('command7'), + (string) $command + ); + } + + protected function command() + { + return IcingaCommand::create( + array( + 'object_name' => $this->testCommandName, + 'object_type' => 'object' + ), + $this->getDb() + ); + } + + protected function loadRendered($name) + { + return file_get_contents(__DIR__ . '/rendered/' . $name . '.out'); + } + + public function tearDown() + { + $db = $this->getDb(); + if (IcingaCommand::exists($this->testCommandName, $db)) { + IcingaCommand::load($this->testCommandName, $db)->delete(); + } + } +} diff --git a/test/php/library/Director/Objects/IcingaHostTest.php b/test/php/library/Director/Objects/IcingaHostTest.php new file mode 100644 index 0000000..b4902bf --- /dev/null +++ b/test/php/library/Director/Objects/IcingaHostTest.php @@ -0,0 +1,771 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\Data\PropertiesFilter\ArrayCustomVariablesFilter; +use Icinga\Module\Director\Data\PropertiesFilter\CustomVariablesFilter; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; +use Icinga\Module\Director\Objects\DirectorDatafield; +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Objects\IcingaHostGroup; +use Icinga\Module\Director\Objects\IcingaZone; +use Icinga\Module\Director\Test\BaseTestCase; +use Icinga\Exception\IcingaException; + +class IcingaHostTest extends BaseTestCase +{ + protected $testHostName = '___TEST___host'; + protected $testDatafieldName = 'test5'; + + public function testPropertiesCanBeSet() + { + $host = $this->host(); + $host->display_name = 'Something else'; + $this->assertEquals( + $host->display_name, + 'Something else' + ); + } + + public function testCanBeReplaced() + { + $host = $this->host(); + $newHost = IcingaHost::create( + array('display_name' => 'Replaced display'), + $this->getDb() + ); + + $this->assertEquals( + count($host->vars()), + 4 + ); + $this->assertEquals( + $host->address, + '127.0.0.127' + ); + + $host->replaceWith($newHost); + $this->assertEquals( + $host->display_name, + 'Replaced display' + ); + $this->assertEquals( + $host->address, + null + ); + + $this->assertEquals( + count($host->vars()), + 0 + ); + } + + public function testCanBeMerged() + { + $host = $this->host(); + $newHost = IcingaHost::create( + array('display_name' => 'Replaced display'), + $this->getDb() + ); + + $this->assertEquals( + count($host->vars()), + 4 + ); + $this->assertEquals( + $host->address, + '127.0.0.127' + ); + + $host->merge($newHost); + $this->assertEquals( + $host->display_name, + 'Replaced display' + ); + $this->assertEquals( + $host->address, + '127.0.0.127' + ); + $this->assertEquals( + count($host->vars()), + 4 + ); + } + + public function testPropertiesCanBePreservedWhenBeingReplaced() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $this->host()->store($db); + $host = IcingaHost::load($this->testHostName, $db); + + $newHost = IcingaHost::create( + array( + 'display_name' => 'Replaced display', + 'address' => '1.2.2.3', + 'vars' => array( + 'test1' => 'newstring', + 'test2' => 18, + 'initially' => 'set and then preserved', + ) + ), + $this->getDb() + ); + + $preserve = array('address', 'vars.test1', 'vars.initially'); + $host->replaceWith($newHost, $preserve); + $this->assertEquals( + $host->address, + '127.0.0.127' + ); + + $this->assertEquals( + $host->{'vars.test2'}, + 18 + ); + + $this->assertEquals( + $host->vars()->test2->getValue(), + 18 + ); + + $this->assertEquals( + $host->{'vars.initially'}, + 'set and then preserved' + ); + + $this->assertFalse( + array_key_exists('address', $host->getModifiedProperties()), + 'Preserved property stays unmodified' + ); + + $newHost->set('vars.initially', 'changed later on'); + $newHost->set('vars.test2', 19); + + $host->replaceWith($newHost, $preserve); + $this->assertEquals( + $host->{'vars.initially'}, + 'set and then preserved' + ); + + $this->assertEquals( + $host->get('vars.test2'), + 19 + ); + + + $host->delete(); + } + + public function testDistinctCustomVarsCanBeSetWithoutSideEffects() + { + $host = $this->host(); + $host->set('vars.test2', 18); + $this->assertEquals( + $host->vars()->test1->getValue(), + 'string' + ); + $this->assertEquals( + $host->vars()->test2->getValue(), + 18 + ); + $this->assertEquals( + $host->vars()->test3->getValue(), + false + ); + } + + public function testVarsArePersisted() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $this->host()->store($db); + $host = IcingaHost::load($this->testHostName, $db); + $this->assertEquals( + $host->vars()->test1->getValue(), + 'string' + ); + $this->assertEquals( + $host->vars()->test2->getValue(), + 17 + ); + $this->assertEquals( + $host->vars()->test3->getValue(), + false + ); + $this->assertEquals( + $host->vars()->test4->getValue(), + (object) array( + 'this' => 'is', + 'a' => array( + 'dict', + 'ionary' + ) + ) + ); + } + + public function testRendersCorrectly() + { + $this->assertEquals( + (string) $this->host(), + $this->loadRendered('host1') + ); + } + + public function testGivesPlainObjectWithInvalidUnresolvedDependencies() + { + $props = $this->getDummyRelatedProperties(); + + $host = $this->host(); + foreach ($props as $k => $v) { + $host->$k = $v; + } + + $plain = $host->toPlainObject(); + foreach ($props as $k => $v) { + $this->assertEquals($plain->$k, $v); + } + } + + public function testCorrectlyStoresLazyRelations() + { + if ($this->skipForMissingDb()) { + return; + } + $db = $this->getDb(); + $host = $this->host(); + $host->zone = '___TEST___zone'; + $this->assertEquals( + '___TEST___zone', + $host->zone + ); + + $zone = $this->newObject('zone', '___TEST___zone'); + $zone->store($db); + + $host->store($db); + $host->delete(); + $zone->delete(); + } + + /** + * @expectedException \RuntimeException + */ + public function testFailsToStoreWithMissingLazyRelations() + { + if ($this->skipForMissingDb()) { + return; + } + $db = $this->getDb(); + $host = $this->host(); + $host->zone = '___TEST___zone'; + $host->store($db); + } + + public function testHandlesUnmodifiedProperties() + { + $this->markTestSkipped('Currently broken, needs to be fixed'); + + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $host = $this->host(); + $host->store($db); + + $parent = $this->newObject('host', '___TEST___parent'); + $parent->store($db); + $host->imports = '___TEST___parent'; + + $host->store($db); + + $plain = $host->getPlainUnmodifiedObject(); + $this->assertEquals( + 'string', + $plain->vars->test1 + ); + $host->vars()->set('test1', 'nada'); + + $host->store(); + + $plain = $host->getPlainUnmodifiedObject(); + $this->assertEquals( + 'nada', + $plain->vars->test1 + ); + + $host->vars()->set('test1', 'string'); + $plain = $host->getPlainUnmodifiedObject(); + $this->assertEquals( + 'nada', + $plain->vars->test1 + ); + + $plain = $host->getPlainUnmodifiedObject(); + $test = IcingaHost::create((array) $plain); + + $this->assertEquals( + $this->loadRendered('host3'), + (string) $test + ); + + $host->delete(); + $parent->delete(); + } + + public function testRendersWithInvalidUnresolvedDependencies() + { + $newHost = $this->host(); + $newHost->zone = 'invalid'; + $newHost->check_command = 'unknown'; + $newHost->event_command = 'What event?'; + $newHost->check_period = 'Not time is a good time @ nite'; + $newHost->command_endpoint = 'nirvana'; + + $this->assertEquals( + (string) $newHost, + $this->loadRendered('host2') + ); + } + + /** + * @expectedException \RuntimeException + */ + public function testFailsToStoreWithInvalidUnresolvedDependencies() + { + if ($this->skipForMissingDb()) { + return; + } + + $host = $this->host(); + $host->zone = 'invalid'; + $host->store($this->getDb()); + } + + public function testRendersToTheCorrectZone() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $host = $this->host()->setConnection($db); + $masterzone = $db->getMasterZoneName(); + + $config = new IcingaConfig($db); + $host->renderToConfig($config); + $this->assertEquals( + array('zones.d/' . $masterzone . '/hosts.conf'), + $config->getFileNames() + ); + + $zone = $this->newObject('zone', '___TEST___zone'); + $zone->store($db); + + $config = new IcingaConfig($db); + $host->zone = '___TEST___zone'; + $host->renderToConfig($config); + $this->assertEquals( + array('zones.d/___TEST___zone/hosts.conf'), + $config->getFileNames() + ); + + $host->has_agent = true; + $host->master_should_connect = true; + $host->accept_config = true; + + $config = new IcingaConfig($db); + $host->renderToConfig($config); + $this->assertEquals( + array( + 'zones.d/___TEST___zone/hosts.conf', + 'zones.d/___TEST___zone/agent_endpoints.conf', + 'zones.d/___TEST___zone/agent_zones.conf' + ), + $config->getFileNames() + ); + + $host->object_type = 'template'; + $host->zone_id = null; + + $config = new IcingaConfig($db); + $host->renderToConfig($config); + $this->assertEquals( + array('zones.d/director-global/host_templates.conf'), + $config->getFileNames() + ); + } + + public function testWhetherTwoHostsCannotBeStoredWithTheSameApiKey() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $a = IcingaHost::create(array( + 'object_name' => '___TEST___a', + 'object_type' => 'object', + 'api_key' => 'a' + ), $db); + $b = IcingaHost::create(array( + 'object_name' => '___TEST___b', + 'object_type' => 'object', + 'api_key' => 'a' + ), $db); + + $a->store(); + try { + $b->store(); + } catch (\RuntimeException $e) { + $msg = $e->getMessage(); + $matchMysql = strpos( + $msg, + "Duplicate entry 'a' for key 'api_key'" + ) !== false; + + $matchPostgres = strpos( + $msg, + 'Unique violation' + ) !== false; + + $this->assertTrue( + $matchMysql || $matchPostgres, + 'Exception message does not tell about unique constraint violation' + ); + $a->delete(); + } + } + + public function testWhetherHostCanBeLoadedWithValidApiKey() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $a = IcingaHost::create(array( + 'object_name' => '___TEST___a', + 'object_type' => 'object', + 'api_key' => 'a1a1a1' + ), $db); + $b = IcingaHost::create(array( + 'object_name' => '___TEST___b', + 'object_type' => 'object', + 'api_key' => 'b1b1b1' + ), $db); + $a->store(); + $b->store(); + + $this->assertEquals( + IcingaHost::loadWithApiKey('b1b1b1', $db)->object_name, + '___TEST___b' + ); + + $a->delete(); + $b->delete(); + } + + /** + * @expectedException \Icinga\Exception\NotFoundError + */ + public function testWhetherInvalidApiKeyThrows404() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + IcingaHost::loadWithApiKey('No___such___key', $db); + } + + public function testEnumProperties() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $properties = IcingaHost::enumProperties($db); + + $this->assertEquals( + array( + 'Host properties' => $this->getDefaultHostProperties() + ), + $properties + ); + } + + public function testEnumPropertiesWithCustomVars() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $host = $this->host(); + $host->store($db); + + $properties = IcingaHost::enumProperties($db); + $this->assertEquals( + array( + 'Host properties' => $this->getDefaultHostProperties(), + 'Custom variables' => array( + 'vars.test1' => 'test1', + 'vars.test2' => 'test2', + 'vars.test3' => 'test3', + 'vars.test4' => 'test4' + ) + ), + $properties + ); + } + + public function testEnumPropertiesWithPrefix() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $host = $this->host(); + $host->store($db); + + $properties = IcingaHost::enumProperties($db, 'host.'); + $this->assertEquals( + array( + 'Host properties' => $this->getDefaultHostProperties('host.'), + 'Custom variables' => array( + 'host.vars.test1' => 'test1', + 'host.vars.test2' => 'test2', + 'host.vars.test3' => 'test3', + 'host.vars.test4' => 'test4' + ) + ), + $properties + ); + } + + public function testEnumPropertiesWithFilter() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + DirectorDatafield::create(array( + 'varname' => $this->testDatafieldName, + 'caption' => 'Blah', + 'description' => '', + 'datatype' => 'Icinga\Module\Director\DataType\DataTypeArray', + 'format' => 'json' + ))->store($db); + + $host = $this->host(); + $host->{'vars.test5'} = array('a', '1'); + $host->store($db); + + $properties = IcingaHost::enumProperties($db, '', new CustomVariablesFilter()); + $this->assertEquals( + array( + 'Custom variables' => array( + 'vars.test1' => 'test1', + 'vars.test2' => 'test2', + 'vars.test3' => 'test3', + 'vars.test4' => 'test4', + 'vars.test5' => 'test5 (Blah)' + ) + ), + $properties + ); + } + + public function testEnumPropertiesWithArrayFilter() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + DirectorDatafield::create(array( + 'varname' => $this->testDatafieldName, + 'caption' => 'Blah', + 'description' => '', + 'datatype' => 'Icinga\Module\Director\DataType\DataTypeArray', + 'format' => 'json' + ))->store($db); + + $host = $this->host(); + $host->{'vars.test5'} = array('a', '1'); + $host->store($db); + + $properties = IcingaHost::enumProperties($db, '', new ArrayCustomVariablesFilter()); + $this->assertEquals( + array( + 'Custom variables' => array( + 'vars.test5' => 'test5 (Blah)' + ) + ), + $properties + ); + } + + public function testMergingObjectKeepsGroupsIfNotGiven() + { + $one = IcingaHostGroup::create([ + 'object_name' => 'one', + 'object_type' => 'object', + ]); + $two = IcingaHostGroup::create([ + 'object_name' => 'two', + 'object_type' => 'object', + ]); + $a = IcingaHost::create([ + 'object_name' => 'one', + 'object_type' => 'object', + 'imports' => [], + 'address' => '127.0.0.2', + 'groups' => [$one, $two] + ]); + + $b = IcingaHost::create([ + 'object_name' => 'one', + 'object_type' => 'object', + 'imports' => [], + 'address' => '127.0.0.42', + ]); + + $a->merge($b); + $this->assertEquals( + '127.0.0.42', + $a->get('address') + ); + $this->assertEquals( + ['one', 'two'], + $a->getGroups() + ); + } + + protected function getDummyRelatedProperties() + { + return array( + 'zone' => 'invalid', + 'check_command' => 'unknown', + 'event_command' => 'What event?', + 'check_period' => 'Not time is a good time @ nite', + 'command_endpoint' => 'nirvana', + ); + } + + protected function host() + { + return IcingaHost::create(array( + 'object_name' => $this->testHostName, + 'object_type' => 'object', + 'address' => '127.0.0.127', + 'display_name' => 'Whatever', + 'vars' => array( + 'test1' => 'string', + 'test2' => 17, + 'test3' => false, + 'test4' => (object) array( + 'this' => 'is', + 'a' => array( + 'dict', + 'ionary' + ) + ) + ) + ), $this->getDb()); + } + + protected function getDefaultHostProperties($prefix = '') + { + return array( + "${prefix}name" => "name", + "${prefix}action_url" => "action_url", + "${prefix}address" => "address", + "${prefix}address6" => "address6", + "${prefix}api_key" => "api_key", + "${prefix}check_command" => "check_command", + "${prefix}check_interval" => "check_interval", + "${prefix}check_period" => "check_period", + "${prefix}check_timeout" => "check_timeout", + "${prefix}command_endpoint" => "command_endpoint", + "${prefix}display_name" => "display_name", + "${prefix}enable_active_checks" => "enable_active_checks", + "${prefix}enable_event_handler" => "enable_event_handler", + "${prefix}enable_flapping" => "enable_flapping", + "${prefix}enable_notifications" => "enable_notifications", + "${prefix}enable_passive_checks" => "enable_passive_checks", + "${prefix}enable_perfdata" => "enable_perfdata", + "${prefix}event_command" => "event_command", + "${prefix}flapping_threshold_high" => "flapping_threshold_high", + "${prefix}flapping_threshold_low" => "flapping_threshold_low", + "${prefix}icon_image" => "icon_image", + "${prefix}icon_image_alt" => "icon_image_alt", + "${prefix}max_check_attempts" => "max_check_attempts", + "${prefix}notes" => "notes", + "${prefix}notes_url" => "notes_url", + "${prefix}retry_interval" => "retry_interval", + "${prefix}volatile" => "volatile", + "${prefix}zone" => "zone", + "${prefix}groups" => "Groups", + "${prefix}templates" => "templates" + ); + } + protected function loadRendered($name) + { + return file_get_contents(__DIR__ . '/rendered/' . $name . '.out'); + } + + public function tearDown() + { + if ($this->hasDb()) { + $db = $this->getDb(); + $kill = array($this->testHostName, '___TEST___parent', '___TEST___a', '___TEST___b'); + foreach ($kill as $name) { + if (IcingaHost::exists($name, $db)) { + IcingaHost::load($name, $db)->delete(); + } + } + + $kill = array('___TEST___zone'); + foreach ($kill as $name) { + if (IcingaZone::exists($name, $db)) { + IcingaZone::load($name, $db)->delete(); + } + } + + $this->deleteDatafields(); + } + } + + protected function deleteDatafields() + { + $db = $this->getDb(); + $dbAdapter = $db->getDbAdapter(); + $kill = array($this->testDatafieldName); + + foreach ($kill as $name) { + $query = $dbAdapter->select() + ->from('director_datafield') + ->where('varname = ?', $name); + foreach (DirectorDatafield::loadAll($db, $query, 'id') as $datafield) { + $datafield->delete(); + } + } + } +} diff --git a/test/php/library/Director/Objects/IcingaNotificationTest.php b/test/php/library/Director/Objects/IcingaNotificationTest.php new file mode 100644 index 0000000..9d9436a --- /dev/null +++ b/test/php/library/Director/Objects/IcingaNotificationTest.php @@ -0,0 +1,248 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Objects\IcingaService; +use Icinga\Module\Director\Objects\IcingaNotification; +use Icinga\Module\Director\Objects\IcingaUser; +use Icinga\Module\Director\Objects\IcingaUsergroup; +use Icinga\Module\Director\Test\BaseTestCase; + +class IcingaNotificationTest extends BaseTestCase +{ + protected $testUserName1 = '___TEST___user1'; + + protected $testUserName2 = '___TEST___user2'; + + protected $testNotificationName = '___TEST___notification'; + + public function testPropertiesCanBeSet() + { + $n = $this->notification(); + $n->notification_interval = '10m'; + $this->assertEquals( + $n->notification_interval, + 600 + ); + } + + public function testCanBeStoredAndDeletedWithRelatedUserPassedAsString() + { + if ($this->skipForMissingDb()) { + return; + } + $db = $this->getDb(); + + $user = $this->user1(); + $user->store($db); + + $n = $this->notification(); + $n->users = $user->object_name; + $this->assertTrue($n->store($db)); + $this->assertTrue($n->delete()); + $user->delete(); + } + + public function testCanBeStoredAndDeletedWithMultipleRelatedUsers() + { + if ($this->skipForMissingDb()) { + return; + } + $db = $this->getDb(); + + $user1 = $this->user1(); + $user1->store($db); + + $user2 = $this->user2(); + $user2->store($db); + + $n = $this->notification(); + $n->users = array($user1->object_name, $user2->object_name); + $this->assertTrue($n->store($db)); + $this->assertTrue($n->delete()); + $user1->delete(); + $user2->delete(); + } + + public function testGivesPlainObjectWithRelatedUsers() + { + if ($this->skipForMissingDb()) { + return; + } + $db = $this->getDb(); + + $user1 = $this->user1(); + $user1->store($db); + + $user2 = $this->user2(); + $user2->store($db); + + $n = $this->notification(); + $n->users = array($user1->object_name, $user2->object_name); + $n->store($db); + $this->assertEquals( + (object) array( + 'object_name' => $this->testNotificationName, + 'object_type' => 'object', + 'users' => array( + $user1->object_name, + $user2->object_name + ) + ), + $n->toPlainObject(false, true) + ); + + $n = IcingaNotification::load($n->object_name, $db); + $this->assertEquals( + (object) array( + 'object_name' => $this->testNotificationName, + 'object_type' => 'object', + 'users' => array( + $user1->object_name, + $user2->object_name + ) + ), + $n->toPlainObject(false, true) + ); + $this->assertEquals( + array(), + $n->toPlainObject()->user_groups + ); + $n->delete(); + + $user1->delete(); + $user2->delete(); + } + + public function testHandlesChangesForStoredRelations() + { + if ($this->skipForMissingDb()) { + return; + } + $db = $this->getDb(); + + $user1 = $this->user1(); + $user1->store($db); + + $user2 = $this->user2(); + $user2->store($db); + + $n = $this->notification(); + $n->users = array($user1->object_name, $user2->object_name); + $n->store($db); + + $n = IcingaNotification::load($n->object_name, $db); + $this->assertFalse($n->hasBeenModified()); + + $n->users = array($user2->object_name); + $this->assertTrue($n->hasBeenModified()); + + $n->store(); + + $n = IcingaNotification::load($n->object_name, $db); + $this->assertEquals( + array($user2->object_name), + $n->users + ); + + $n->users = array(); + $n->store(); + + $n = IcingaNotification::load($n->object_name, $db); + $this->assertEquals( + array(), + $n->users + ); + + // Should be fixed with lazy loading: + // $n->users = array($user1->object_name, $user2->object_name); + // $this->assertFalse($n->hasBeenModified()); + + $n->delete(); + + $user1->delete(); + $user2->delete(); + } + + public function testRendersConfigurationWithRelatedUsers() + { + if ($this->skipForMissingDb()) { + return; + } + $db = $this->getDb(); + + $user1 = $this->user1(); + $user1->store($db); + + $user2 = $this->user2(); + $user2->store($db); + + $n = $this->notification(); + $n->users = array($user1->object_name, $user2->object_name); + + $this->assertEquals( + $this->loadRendered('notification1'), + (string) $n + ); + } + + public function testLazyUsersCanBeSet() + { + $this->markTestSkipped('Setting lazy properties not yet completed'); + + $n = $this->notification(); + $n->users = 'bla'; + } + + protected function user1() + { + return IcingaUser::create(array( + 'object_name' => $this->testUserName1, + 'object_type' => 'object', + 'email' => 'nowhere@example.com', + ), $this->getDb()); + } + + protected function user2() + { + return IcingaUser::create(array( + 'object_name' => $this->testUserName2, + 'object_type' => 'object', + 'email' => 'nowhere.else@example.com', + ), $this->getDb()); + } + + protected function notification() + { + return IcingaNotification::create(array( + 'object_name' => $this->testNotificationName, + 'object_type' => 'object', + ), $this->getDb()); + } + + protected function loadRendered($name) + { + return file_get_contents(__DIR__ . '/rendered/' . $name . '.out'); + } + + public function tearDown() + { + if ($this->hasDb()) { + $db = $this->getDb(); + $kill = array($this->testNotificationName); + foreach ($kill as $name) { + if (IcingaNotification::exists($name, $db)) { + IcingaNotification::load($name, $db)->delete(); + } + } + + $kill = array($this->testUserName1, $this->testUserName2); + foreach ($kill as $name) { + if (IcingaUser::exists($name, $db)) { + IcingaUser::load($name, $db)->delete(); + } + } + } + } +} diff --git a/test/php/library/Director/Objects/IcingaServiceSetTest.php b/test/php/library/Director/Objects/IcingaServiceSetTest.php new file mode 100644 index 0000000..ad7c135 --- /dev/null +++ b/test/php/library/Director/Objects/IcingaServiceSetTest.php @@ -0,0 +1,183 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\Objects\IcingaServiceSet; +use Icinga\Module\Director\Test\IcingaObjectTestCase; + +class IcingaServiceSetTest extends IcingaObjectTestCase +{ + protected $table = 'icinga_service_set'; + protected $testObjectName = '___TEST___set'; + + public function setUp() + { + $this->assertNull($this->subject, 'subject must have been taken down before!'); + + if ($this->hasDb()) { + $this->subject = IcingaServiceSet::create(array( + 'object_name' => $this->testObjectName, + 'object_type' => 'template', + )); + $this->subject->store($this->getDb()); + } + } + + public function testUpdatingSet() + { + $set = IcingaServiceSet::load($this->testObjectName, $this->getDb()); + $this->assertTrue($set->hasBeenLoadedFromDb()); + + $set->set('description', 'This is a set created by Phpunit!'); + $this->assertTrue($set->hasBeenModified()); + $set->store(); + + $set->set('assign_filter', 'host.name=foobar'); + $this->assertTrue($set->hasBeenModified()); + $set->store(); + + $this->assertFalse($set->hasBeenModified()); + } + + public function testAddingSetToHost() + { + $host = $this->createObject('for_set', 'icinga_host', array( + 'object_type' => 'object', + 'address' => '1.2.3.4', + )); + + $set = IcingaServiceSet::create(array( + 'object_name' => $this->testObjectName, + 'object_type' => 'object', + ), $this->getDb()); // TODO: fails if db not set here... + + $set->setImports($this->testObjectName); + $this->assertTrue($set->hasBeenModified()); + $this->assertEquals(array($this->testObjectName), $set->getImports()); + + $set->set('host', $host->getObjectName()); + + $set->store(); + $this->prepareObjectTearDown($set); + $this->assertFalse($set->hasBeenModified()); + } + + public function testDeletingHostWithSet() + { + $this->createObject('for_set', 'icinga_host', array( + 'object_type' => 'object', + 'address' => '1.2.3.4', + ), false)->store(); + + $host = $this->loadObject('for_set', 'icinga_host'); + $host->delete(); + + $this->checkForDanglingHostSets(); + } + + public function testAddingServicesToSet() + { + $set = IcingaServiceSet::load($this->testObjectName, $this->getDb()); + + // TODO: setting service_set by name should work too... + + $serviceA = $this->createObject('serviceA', 'icinga_service', array( + 'object_type' => 'apply', + 'service_set_id' => $set->getAutoincId(), + )); + $nameA = $serviceA->getObjectName(); + + $serviceB = $this->createObject('serviceB', 'icinga_service', array( + 'object_type' => 'apply', + 'service_set_id' => $set->getAutoincId(), + )); + $nameB = $serviceB->getObjectName(); + + $services = $set->getServiceObjects(); + + $this->assertCount(2, $services); + $this->assertArrayHasKey($nameA, $services); + $this->assertArrayHasKey($nameB, $services); + $this->assertEquals($serviceA->getAutoincId(), $services[$nameA]->getAutoincId()); + $this->assertEquals($serviceB->getAutoincId(), $services[$nameB]->getAutoincId()); + + // TODO: deleting set should delete services + + $this->checkForDanglingServices(); + } + + /** + * @expectedException \RuntimeException + */ + public function testCreatingSetWithoutType() + { + $set = IcingaServiceSet::create(array( + 'object_name' => '___TEST__set_BAD', + )); + $set->store($this->getDb()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCreatingServiceSetWithoutHost() + { + $set = IcingaServiceSet::create(array( + 'object_name' => '___TEST__set_BAD2', + 'object_type' => 'object', + )); + + $set->store($this->getDb()); + } + + public function testDeletingSet() + { + $set = IcingaServiceSet::load($this->testObjectName, $this->getDb()); + $set->delete(); + + $this->assertFalse(IcingaServiceSet::exists($this->testObjectName, $this->getDb())); + $this->subject = null; + } + + public function checkForDanglingServices() + { + $db = $this->getDb()->getDbAdapter(); + $query = $db->select() + ->from(array('s' => 'icinga_service'), array('id')) + ->joinLeft( + array('ss' => 'icinga_service_set'), + 'ss.id = s.service_set_id', + array() + ) + ->where('s.service_set_id IS NOT NULL') + ->where('ss.id IS NULL'); + + $ids = $db->fetchCol($query); + + $this->assertEmpty($ids, sprintf('Found dangling service_set services in database: %s', join(', ', $ids))); + } + + public function checkForDanglingHostSets() + { + $db = $this->getDb()->getDbAdapter(); + $query = $db->select() + ->from(array('ss' => 'icinga_service_set'), array('id')) + ->joinLeft( + array('h' => 'icinga_host'), + 'h.id = ss.host_id', + array() + ) + ->where('ss.host_id IS NOT NULL') + ->where('h.id IS NULL'); + + $ids = $db->fetchCol($query); + + $this->assertEmpty( + $ids, + sprintf( + 'Found dangling service_set\'s for a host, without the host in database: %s', + join(', ', $ids) + ) + ); + } +} diff --git a/test/php/library/Director/Objects/IcingaServiceTest.php b/test/php/library/Director/Objects/IcingaServiceTest.php new file mode 100644 index 0000000..468db67 --- /dev/null +++ b/test/php/library/Director/Objects/IcingaServiceTest.php @@ -0,0 +1,293 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\IcingaConfig\IcingaConfig; +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Objects\IcingaService; +use Icinga\Module\Director\Test\BaseTestCase; + +class IcingaServiceTest extends BaseTestCase +{ + protected $testHostName = '___TEST___host'; + + protected $testServiceName = '___TEST___service'; + + protected $createdServices = array(); + + public function testUnstoredHostCanBeLazySet() + { + $service = $this->service(); + $service->display_name = 'Something else'; + $service->host = 'not yet'; + $this->assertEquals( + 'not yet', + $service->host + ); + } + + /** + * @expectedException \RuntimeException + */ + public function testFailsToStoreWithMissingLazyRelations() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $service = $this->service(); + $service->display_name = 'Something else'; + $service->host = 'not yet'; + $service->store($db); + $service->delete(); + } + + public function testAcceptsAssignRules() + { + $service = $this->service(); + $service->object_type = 'apply'; + $service->assign_filter = 'host.address="127.*"'; + } + + /** + * @expectedException \LogicException + */ + public function testRefusesAssignRulesWhenNotBeingAnApply() + { + $service = $this->service(); + $service->assign_filter = 'host.address=127.*'; + } + + public function testAcceptsAndRendersFlatAssignRules() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $service = $this->service(); + + // Service apply rule rendering requires access to settings: + $service->setConnection($db); + $service->object_type = 'apply'; + $service->assign_filter = 'host.address="127.*"|host.vars.env="test"'; + + $this->assertEquals( + $this->loadRendered('service1'), + (string) $service + ); + + $this->assertEquals( + 'host.address="127.*"|host.vars.env="test"', + $service->assign_filter + ); + } + + public function testAcceptsAndRendersStructuredAssignRules() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $service = $this->service(); + // Service apply rule rendering requires access to settings: + $service->setConnection($db); + $service->object_type = 'apply'; + $service->assign_filter = 'host.address="127.*"|host.vars.env="test"'; + + $this->assertEquals( + $this->loadRendered('service1'), + (string) $service + ); + + $this->assertEquals( + 'host.address="127.*"|host.vars.env="test"', + $service->assign_filter = 'host.address="127.*"|host.vars.env="test"' + ); + } + + public function testPersistsAssignRules() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $service = $this->service(); + $service->object_type = 'apply'; + $service->assign_filter = 'host.address="127.*"|host.vars.env="test"'; + + $service->store($db); + + $service = IcingaService::loadWithAutoIncId($service->id, $db); + $this->assertEquals( + $this->loadRendered('service1'), + (string) $service + ); + + $this->assertEquals( + 'host.address="127.*"|host.vars.env="test"', + $service->assign_filter + ); + + $service->delete(); + } + + public function testRendersToTheCorrectZone() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + $service = $this->service()->setConnection($db); + + $config = new IcingaConfig($db); + $service->renderToConfig($config); + $masterzone = $db->getMasterZoneName(); + $this->assertEquals( + array('zones.d/' . $masterzone . '/services.conf'), + $config->getFileNames() + ); + } + + public function testVariablesInPropertiesAndCustomVariables() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $service = $this->service('___TEST___service_$not_replaced$'); + $service->setConnection($db); + $service->object_type = 'apply'; + $service->display_name = 'Service: $host.vars.replaced$'; + $service->assign_filter = 'host.address="127.*"'; + $service->{'vars.custom_var'} = '$host.vars.replaced$'; + + $this->assertEquals( + $this->loadRendered('service3'), + (string) $service + ); + } + + public function testVariablesAreNotReplacedForNonApplyObjects() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $host = $this->host(); + $host->store($db); + + $service = $this->service('___TEST___service_$not_replaced$'); + $service->object_type = 'object'; + $service->host_id = $host->get('id'); + $service->display_name = 'Service: $host.vars.not_replaced$'; + $service->{'vars.custom_var'} = '$host.vars.not_replaced$'; + $service->store($db); + + $service = IcingaService::loadWithAutoIncId($service->id, $db); + $this->assertEquals( + $this->loadRendered('service4'), + (string) $service + ); + } + + public function testApplyForRendersInVariousModes() + { + if ($this->skipForMissingDb()) { + return; + } + + $db = $this->getDb(); + + $service = $this->service()->setConnection($db); + $service->object_type = 'apply'; + $service->apply_for = 'host.vars.test1'; + $service->assign_filter = 'host.vars.env="test"'; + $this->assertEquals( + $this->loadRendered('service5'), + (string) $service + ); + + $service->object_name = '___TEST$config$___service $host.var.bla$'; + $this->assertEquals( + $this->loadRendered('service6'), + (string) $service + ); + + $service->object_name = ''; + $this->assertEquals( + $this->loadRendered('service7'), + (string) $service + ); + } + + protected function host() + { + return IcingaHost::create(array( + 'object_name' => $this->testHostName, + 'object_type' => 'object', + 'address' => '127.0.0.1', + )); + } + + protected function service($objectName = null) + { + if ($objectName === null) { + $objectName = $this->testServiceName; + } + $this->createdServices[] = $objectName; + return IcingaService::create(array( + 'object_name' => $objectName, + 'object_type' => 'object', + 'display_name' => 'Whatever service', + 'vars' => array( + 'test1' => 'string', + 'test2' => 17, + 'test3' => false, + 'test4' => (object) array( + 'this' => 'is', + 'a' => array( + 'dict', + 'ionary' + ) + ) + ) + )); + } + + protected function loadRendered($name) + { + return file_get_contents(__DIR__ . '/rendered/' . $name . '.out'); + } + + public function tearDown() + { + if ($this->hasDb()) { + $db = $this->getDb(); + $kill = array($this->testHostName); + foreach ($kill as $name) { + if (IcingaHost::exists($name, $db)) { + IcingaHost::load($name, $db)->delete(); + } + } + + $kill = $this->createdServices; + foreach ($kill as $name) { + if (IcingaService::exists(array($name), $db)) { + IcingaService::load($name, $db)->delete(); + } + } + } + } +} diff --git a/test/php/library/Director/Objects/IcingaTemplateResolverTest.php b/test/php/library/Director/Objects/IcingaTemplateResolverTest.php new file mode 100644 index 0000000..09d0ead --- /dev/null +++ b/test/php/library/Director/Objects/IcingaTemplateResolverTest.php @@ -0,0 +1,158 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Test\BaseTestCase; + +class IcingaTemplateResolverTest extends BaseTestCase +{ + /** @var IcingaHost[] */ + private $scenario; + + private $prefix = '__TEST_i1_'; + + public function testParentNamesCanBeRetrieved() + { + $this->assertEquals( + array( + $this->prefixed('t6'), + $this->prefixed('t5'), + $this->prefixed('t2') + ), + $this->getHost('t1')->templateResolver()->listParentNames() + ); + } + + public function testFullInhertancePathIdsAreCorrect() + { + $this->assertEquals( + $this->getIds(array('t5', 't6', 't5', 't5', 't4', 't3', 't5', 't6', 't2', 't1')), + $this->getHost('t1')->templateResolver()->listFullInheritancePathIds() + ); + } + + public function testInhertancePathIdsAreCorrect() + { + $this->assertEquals( + $this->getIds(array('t4', 't3', 't5', 't6', 't2', 't1')), + $this->getHost('t1')->templateResolver()->listInheritancePathIds() + ); + } + + protected function getHost($name) + { + $hosts = $this->getScenario(); + return $hosts[$name]; + } + + protected function getId($name) + { + $hosts = $this->getScenario(); + return $hosts[$name]->id; + } + + protected function getIds($names) + { + $ids = array(); + foreach ($names as $name) { + $ids[] = $this->getId($name); + } + + return $ids; + } + + protected function prefixed($name) + { + return $this->prefix . $name; + } + + /** + * @return IcingaHost[] + */ + protected function getScenario() + { + if ($this->scenario === null) { + $this->scenario = $this->createScenario(); + } + + return $this->scenario; + } + + /** + * @return IcingaHost[] + */ + protected function createScenario() + { + // Defionition imports (object -> imported) + // t1 -> t6, t5, t2 + // t6 -> t5 + // t3 -> t4 + // t2 -> t3, t6 + // t4 -> t5 + + // 5, 6, 5, 5, 4, 3, 5, 6, 2, 1 + + $t1 = $this->create('t1'); + $t4 = $this->create('t4'); + $t3 = $this->create('t3'); + $t2 = $this->create('t2'); + $t5 = $this->create('t5'); + $t6 = $this->create('t6'); + + // TODO: Must work without this: + $t1->templateResolver()->clearCache(); + $t1->set('imports', array($t6, $t5, $t2)); + $t6->set('imports', array($t5)); + $t3->set('imports', array($t4)); + $t2->set('imports', array($t3, $t6)); + $t4->set('imports', array($t5)); + + $t5->store(); + $t4->store(); + $t3->store(); + $t6->store(); + $t2->store(); + $t1->store(); + + // TODO: Must work without this: + $t1->templateResolver()->clearCache(); + return array( + 't1' => $t1, + 't2' => $t2, + 't3' => $t3, + 't4' => $t4, + 't5' => $t5, + 't6' => $t6, + ); + } + + /** + * @param $name + * @return IcingaHost + */ + protected function create($name) + { + $host = IcingaHost::create( + array( + 'object_name' => $this->prefixed($name), + 'object_type' => 'template' + ) + ); + + $host->store($this->getDb()); + return $host; + } + + public function tearDown() + { + $db = $this->getDb(); + $kill = array('t1', 't2', 't6', 't3', 't4', 't5'); + foreach ($kill as $short) { + $name = $this->prefixed($short); + if (IcingaHost::exists($name, $db)) { + IcingaHost::load($name, $db)->delete(); + } + } + } +} diff --git a/test/php/library/Director/Objects/IcingaTimePeriodTest.php b/test/php/library/Director/Objects/IcingaTimePeriodTest.php new file mode 100644 index 0000000..84496d3 --- /dev/null +++ b/test/php/library/Director/Objects/IcingaTimePeriodTest.php @@ -0,0 +1,184 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\Objects\IcingaTimePeriod; +use Icinga\Module\Director\Test\BaseTestCase; + +class IcingaTimePeriodTest extends BaseTestCase +{ + protected $testPeriodName = '___TEST___timeperiod'; + + protected $createdNames = []; + + public function testWhetherUpdatedTimeperiodsAreCorrectlyStored() + { + if ($this->skipForMissingDb()) { + return; + } + + $period = $this->createTestPeriod(); + + $newRanges = array( + 'monday' => '00:00-24:00', + 'tuesday' => '18:00-24:00', + 'wednesday' => '00:00-24:00', + ); + $period->ranges = $newRanges; + $this->assertEquals( + '18:00-24:00', + $period->toPlainObject()->ranges->tuesday + ); + + $period->store(); + + $period = $this->loadTestPeriod(); + $this->assertEquals( + '18:00-24:00', + $period->ranges()->get('tuesday')->range_value + ); + + $this->assertEquals( + '00:00-24:00', + $period->toPlainObject()->ranges->wednesday + ); + + $period->ranges()->setRange('wednesday', '09:00-17:00'); + + $this->assertEquals( + '09:00-17:00', + $period->toPlainObject()->ranges->wednesday + ); + + $this->assertEquals( + '00:00-24:00', + $period->getPlainUnmodifiedObject()->ranges->wednesday + ); + } + + protected function createTestPeriod($suffix = '', $testRanges = []) + { + $db = $this->getDb(); + $name = $this->testPeriodName . $suffix; + + $this->createdNames[] = $name; + $object = IcingaTimePeriod::create( + array( + 'object_name' => $name, + 'object_type' => 'object' + ), + $db + ); + $object->store(); + $ranges = $object->ranges(); + + if (empty($testRanges)) { + $testRanges = array( + 'monday' => '00:00-24:00', + 'tuesday' => '00:00-24:00', + 'wednesday' => '00:00-24:00', + ); + } + + $ranges->set($testRanges); + $object->store(); + + return $object; + } + + public function testIsActiveWorksForWeekdayRanges() + { + $period = $this->createTestPeriod(); + + $newRanges = array( + 'monday' => '00:00-24:00', + 'tuesday' => '18:00-24:00', + 'wednesday' => '00:00-24:00', + ); + $period->ranges = $newRanges; + + // Tuesday: + $this->assertFalse($period->isActive(strtotime('2016-05-17 10:00:00'))); + // Wednesday: + $this->assertTrue($period->isActive(strtotime('2016-05-18 10:00:00'))); + // Thursday: + $this->assertFalse($period->isActive(strtotime('2016-05-19 10:00:00'))); + } + + public function testPeriodWithIncludes() + { + $period = $this->createTestPeriod(); + $include = $this->createTestPeriod('include', ['thursday' => '00:00-24:00']); + + $period->set('includes', $include->object_name); + $period->store(); + + // Wednesday: + $this->assertTrue($period->isActive(strtotime('2016-05-18 10:00:00'))); + // Thursday: + $this->assertTrue($period->isActive(strtotime('2016-05-19 10:00:00'))); + } + + public function testPeriodWithExcludes() + { + $period = $this->createTestPeriod(); + $exclude = $this->createTestPeriod('exclude', ['wednesday' => '00:00-24:00']); + + $period->set('excludes', $exclude->object_name); + $period->store(); + + // Wednesday: + $this->assertFalse($period->isActive(strtotime('2016-05-18 10:00:00'))); + // Thursday: + $this->assertFalse($period->isActive(strtotime('2016-05-19 10:00:00'))); + } + + public function testPeriodPreferingIncludes() + { + $period = $this->createTestPeriod(); + $include = $this->createTestPeriod('include', ['thursday' => '00:00-24:00']); + $exclude = $this->createTestPeriod('exclude', ['thursday' => '00:00-24:00']); + + $period->set('includes', $include->object_name); + $period->set('excludes', $exclude->object_name); + $period->store(); + + // Wednesday: + $this->assertTrue($period->isActive(strtotime('2016-05-18 10:00:00'))); + // Thursday: + $this->assertTrue($period->isActive(strtotime('2016-05-19 10:00:00'))); + } + + public function testPeriodPreferingExcludes() + { + $period = $this->createTestPeriod(); + $include = $this->createTestPeriod('include', ['thursday' => '00:00-24:00']); + $exclude = $this->createTestPeriod('exclude', ['thursday' => '00:00-24:00']); + + $period->set('prefer_includes', false); + $period->set('includes', $include->object_name); + $period->set('excludes', $exclude->object_name); + $period->store(); + + // Wednesday: + $this->assertTrue($period->isActive(strtotime('2016-05-18 10:00:00'))); + // Thursday: + $this->assertFalse($period->isActive(strtotime('2016-05-19 10:00:00'))); + } + + protected function loadTestPeriod($suffix = '') + { + return IcingaTimePeriod::load($this->testPeriodName . $suffix, $this->getDb()); + } + + public function tearDown() + { + $db = $this->getDb(); + + foreach ($this->createdNames as $name) { + if (IcingaTimePeriod::exists($name, $db)) { + IcingaTimePeriod::load($name, $db)->delete(); + } + } + } +} diff --git a/test/php/library/Director/Objects/rendered/command1.out b/test/php/library/Director/Objects/rendered/command1.out new file mode 100644 index 0000000..12e156f --- /dev/null +++ b/test/php/library/Director/Objects/rendered/command1.out @@ -0,0 +1,4 @@ +object CheckCommand "___TEST___command" { + command = [ "C:\\test.exe" ] +} + diff --git a/test/php/library/Director/Objects/rendered/command2.out b/test/php/library/Director/Objects/rendered/command2.out new file mode 100644 index 0000000..e853285 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/command2.out @@ -0,0 +1,4 @@ +object CheckCommand "___TEST___command" { + command = [ "/tmp/bla" ] +} + diff --git a/test/php/library/Director/Objects/rendered/command3.out b/test/php/library/Director/Objects/rendered/command3.out new file mode 100644 index 0000000..7e7eef9 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/command3.out @@ -0,0 +1,4 @@ +object CheckCommand "___TEST___command" { + command = [ PluginDir + "/tmp/bla" ] +} + diff --git a/test/php/library/Director/Objects/rendered/command4.out b/test/php/library/Director/Objects/rendered/command4.out new file mode 100644 index 0000000..3dc7ac5 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/command4.out @@ -0,0 +1,4 @@ +object CheckCommand "___TEST___command" { + command = [ "\\\\network\\share\\bla.exe" ] +} + diff --git a/test/php/library/Director/Objects/rendered/command5.out b/test/php/library/Director/Objects/rendered/command5.out new file mode 100644 index 0000000..1e57577 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/command5.out @@ -0,0 +1,4 @@ +object CheckCommand "___TEST___command" { + command = [ BlahDir + "\\network\\share\\bla.exe" ] +} + diff --git a/test/php/library/Director/Objects/rendered/command6.out b/test/php/library/Director/Objects/rendered/command6.out new file mode 100644 index 0000000..3f123ce --- /dev/null +++ b/test/php/library/Director/Objects/rendered/command6.out @@ -0,0 +1,4 @@ +object CheckCommand "___TEST___command" { + command = [ PluginDir + "/network\\share\\bla.exe" ] +} + diff --git a/test/php/library/Director/Objects/rendered/command7.out b/test/php/library/Director/Objects/rendered/command7.out new file mode 100644 index 0000000..4f966f0 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/command7.out @@ -0,0 +1,9 @@ +object CheckCommand "___TEST___command" { + command = [ PluginDir + "/bla" ] + arguments += { + "-a" = { + set_if = "$a$" + } + } +} + diff --git a/test/php/library/Director/Objects/rendered/host1.out b/test/php/library/Director/Objects/rendered/host1.out new file mode 100644 index 0000000..3637f2d --- /dev/null +++ b/test/php/library/Director/Objects/rendered/host1.out @@ -0,0 +1,12 @@ +object Host "___TEST___host" { + display_name = "Whatever" + address = "127.0.0.127" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } +} + diff --git a/test/php/library/Director/Objects/rendered/host2.out b/test/php/library/Director/Objects/rendered/host2.out new file mode 100644 index 0000000..74668e1 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/host2.out @@ -0,0 +1,17 @@ +object Host "___TEST___host" { + display_name = "Whatever" + address = "127.0.0.127" + check_command = "unknown" + check_period = "Not time is a good time @ nite" + event_command = "What event?" + zone = "invalid" + command_endpoint = "nirvana" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } +} + diff --git a/test/php/library/Director/Objects/rendered/host3.out b/test/php/library/Director/Objects/rendered/host3.out new file mode 100644 index 0000000..5661ca9 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/host3.out @@ -0,0 +1,14 @@ +object Host "___TEST___host" { + import "___TEST___parent" + + display_name = "Whatever" + address = "127.0.0.127" + vars.test1 = "nada" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } +} + diff --git a/test/php/library/Director/Objects/rendered/notification1.out b/test/php/library/Director/Objects/rendered/notification1.out new file mode 100644 index 0000000..13f06ce --- /dev/null +++ b/test/php/library/Director/Objects/rendered/notification1.out @@ -0,0 +1,4 @@ +object Notification "___TEST___notification" { + users = [ "___TEST___user1", "___TEST___user2" ] +} + diff --git a/test/php/library/Director/Objects/rendered/service1.out b/test/php/library/Director/Objects/rendered/service1.out new file mode 100644 index 0000000..ba65b08 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/service1.out @@ -0,0 +1,14 @@ +apply Service "___TEST___service" { + display_name = "Whatever service" + assign where match("127.*", host.address) || host.vars.env == "test" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } + + import DirectorOverrideTemplate +} + diff --git a/test/php/library/Director/Objects/rendered/service2.out b/test/php/library/Director/Objects/rendered/service2.out new file mode 100644 index 0000000..ea7d901 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/service2.out @@ -0,0 +1,16 @@ +apply Service "___TEST___service" { + display_name = "Whatever service" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } + + assign where match("128.*", host.address) + ignore where host.name == "localhost" + + import DirectorOverrideTemplate +} + diff --git a/test/php/library/Director/Objects/rendered/service3.out b/test/php/library/Director/Objects/rendered/service3.out new file mode 100644 index 0000000..0288ee6 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/service3.out @@ -0,0 +1,15 @@ +apply Service "___TEST___service_$not_replaced$" { + display_name = "Service: " + host.vars.replaced + assign where match("127.*", host.address) + vars.custom_var = "$host.vars.replaced$" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } + + import DirectorOverrideTemplate +} + diff --git a/test/php/library/Director/Objects/rendered/service4.out b/test/php/library/Director/Objects/rendered/service4.out new file mode 100644 index 0000000..aeb280a --- /dev/null +++ b/test/php/library/Director/Objects/rendered/service4.out @@ -0,0 +1,13 @@ +object Service "___TEST___service_$not_replaced$" { + host_name = "___TEST___host" + display_name = "Service: $host.vars.not_replaced$" + vars.custom_var = "$host.vars.not_replaced$" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } +} + diff --git a/test/php/library/Director/Objects/rendered/service5.out b/test/php/library/Director/Objects/rendered/service5.out new file mode 100644 index 0000000..b05e630 --- /dev/null +++ b/test/php/library/Director/Objects/rendered/service5.out @@ -0,0 +1,14 @@ +apply Service "___TEST___service" for (config in host.vars.test1) { + display_name = "Whatever service" + assign where host.vars.env == "test" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } + + import DirectorOverrideTemplate +} + diff --git a/test/php/library/Director/Objects/rendered/service6.out b/test/php/library/Director/Objects/rendered/service6.out new file mode 100644 index 0000000..fdca11c --- /dev/null +++ b/test/php/library/Director/Objects/rendered/service6.out @@ -0,0 +1,15 @@ +apply Service for (config in host.vars.test1) { + name = "___TEST" + config + "___service " + host.var.bla + display_name = "Whatever service" + assign where host.vars.env == "test" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } + + import DirectorOverrideTemplate +} + diff --git a/test/php/library/Director/Objects/rendered/service7.out b/test/php/library/Director/Objects/rendered/service7.out new file mode 100644 index 0000000..c125ccc --- /dev/null +++ b/test/php/library/Director/Objects/rendered/service7.out @@ -0,0 +1,14 @@ +apply Service for (config in host.vars.test1) { + display_name = "Whatever service" + assign where host.vars.env == "test" + vars.test1 = "string" + vars.test2 = 17 + vars.test3 = false + vars.test4 = { + a = [ "dict", "ionary" ] + @this = "is" + } + + import DirectorOverrideTemplate +} + diff --git a/test/php/library/Director/PropertyModifier/PropertyModifierArrayElementByPositionTest.php b/test/php/library/Director/PropertyModifier/PropertyModifierArrayElementByPositionTest.php new file mode 100644 index 0000000..84465f3 --- /dev/null +++ b/test/php/library/Director/PropertyModifier/PropertyModifierArrayElementByPositionTest.php @@ -0,0 +1,143 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\PropertyModifier\PropertyModifierArrayElementByPosition; +use Icinga\Module\Director\Test\BaseTestCase; + +class PropertyModifierArrayElementByPositionTest extends BaseTestCase +{ + /* + * Allowed settings: + * + * position_type: first, last, fixed + * position + * when_missing: fail, null + */ + + public function testGivesFirstElementOfArray() + { + $this->assertEquals( + 'one', + $this->buildModifier('first')->transform(['one', 'two', 'three']) + ); + } + + public function testGivesFirstElementOfObject() + { + $this->assertEquals( + 'one', + $this->buildModifier('first')->transform((object) ['one', 'two', 'three']) + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGettingFirstFailsForEmptyArray() + { + $this->buildModifier('first')->transform([]); + } + + public function testGettingFirstGivesNullForEmptyArray() + { + $this->assertNull($this->buildModifier('first', null, 'null')->transform([])); + } + + public function testGivesLastElementOfArray() + { + $this->assertEquals( + 'three', + $this->buildModifier('last')->transform(['one', 'two', 'three']) + ); + } + + public function testGivesLastElementOfObject() + { + $this->assertEquals( + 'three', + $this->buildModifier('last')->transform((object) ['one', 'two', 'three']) + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGettingLastFailsForEmptyArray() + { + $this->buildModifier('last')->transform([]); + } + + public function testGettingLastGivesNullForEmptyArray() + { + $this->assertNull($this->buildModifier('last', null, 'null')->transform([])); + } + + public function testGivesSpecificElementOfArray() + { + $this->assertEquals( + 'two', + $this->buildModifier('fixed', '1')->transform(['one', 'two', 'three']) + ); + } + + public function testGivesSpecificElementOfObject() + { + $this->assertEquals( + 'two', + $this->buildModifier('fixed', 1)->transform((object) ['one', 'two', 'three']) + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGettingSpecificFailsForEmptyArray() + { + $this->buildModifier('fixed', 1)->transform([]); + } + + public function testGettingSpecificGivesNullForEmptyArray() + { + $this->assertNull($this->buildModifier('fixed', 1, 'null')->transform([])); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGettingSpecificFailsForMissingValue() + { + $this->buildModifier('fixed', 3)->transform(['one', 'two', 'three']); + } + + public function testGettingSpecificGivesNullForMissingValue() + { + $this->assertNull($this->buildModifier('fixed', 3, 'null')->transform(['one', 'two', 'three'])); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFailsForStrings() + { + $this->buildModifier('first')->transform('string'); + } + + public function testAnnouncesArraySupport() + { + $modifier = new PropertyModifierArrayElementByPosition(); + $this->assertTrue($modifier->hasArraySupport()); + } + + protected function buildModifier($type, $position = null, $whenMissing = 'fail') + { + $modifier = new PropertyModifierArrayElementByPosition(); + $modifier->setSettings([ + 'position_type' => $type, + 'position' => $position, + 'when_missing' => $whenMissing, + ]); + + return $modifier; + } +} diff --git a/test/php/library/Director/PropertyModifier/PropertyModifierArrayFilterTest.php b/test/php/library/Director/PropertyModifier/PropertyModifierArrayFilterTest.php new file mode 100644 index 0000000..e50a45d --- /dev/null +++ b/test/php/library/Director/PropertyModifier/PropertyModifierArrayFilterTest.php @@ -0,0 +1,120 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\PropertyModifier\PropertyModifierArrayFilter; +use Icinga\Module\Director\Test\BaseTestCase; + +class PropertyModifierArrayFilterTest extends BaseTestCase +{ + /** + * Allowed settings: + * + * filter_method: wildcard, regex + * filter_string + * + * policy: keep, reject + * when_empty: empty_array, null + */ + + /** @var array */ + private $testArray = array( + 'www.example.com', + 'example.com', + 'www', + 'wwexample.com', + 'example.www', + '', + ); + + public function testKeepMatchingWildcards() + { + $modifier = new PropertyModifierArrayFilter(); + $modifier->setSettings(array( + 'filter_method' => 'wildcard', + 'filter_string' => 'www*', + 'policy' => 'keep', + 'when_empty' => 'empty_array', + )); + + $this->assertEquals( + array('www.example.com', 'www'), + $modifier->transform($this->testArray) + ); + } + + public function testRejectMatchingWildcards() + { + $modifier = new PropertyModifierArrayFilter(); + $modifier->setSettings(array( + 'filter_method' => 'wildcard', + 'filter_string' => 'www*', + 'policy' => 'reject', + 'when_empty' => 'empty_array', + )); + + $this->assertEquals( + array('example.com', 'wwexample.com', 'example.www', ''), + $modifier->transform($this->testArray) + ); + } + + public function testKeepMatchingRegularExpression() + { + $modifier = new PropertyModifierArrayFilter(); + $modifier->setSettings(array( + 'filter_method' => 'regex', + 'filter_string' => '/^w{3}.*/', + 'policy' => 'keep', + 'when_empty' => 'empty_array', + )); + + $this->assertEquals( + array('www.example.com', 'www'), + $modifier->transform($this->testArray) + ); + } + + public function testRejectMatchingRegularExpression() + { + $modifier = new PropertyModifierArrayFilter(); + $modifier->setSettings(array( + 'filter_method' => 'regex', + 'filter_string' => '/^w{3}.*/', + 'policy' => 'reject', + 'when_empty' => 'empty_array', + )); + + $this->assertEquals( + array('example.com', 'wwexample.com', 'example.www', ''), + $modifier->transform($this->testArray) + ); + } + + public function testGivesEmptyArrayOrNullAccordingToConfig() + { + $modifier = new PropertyModifierArrayFilter(); + $modifier->setSettings(array( + 'filter_method' => 'wildcard', + 'filter_string' => 'no-match', + 'policy' => 'keep', + 'when_empty' => 'empty_array', + )); + + $this->assertEquals( + array(), + $modifier->transform($this->testArray) + ); + + $modifier->setSetting('when_empty', 'null'); + $this->assertNull( + $modifier->transform($this->testArray) + ); + } + + public function testAnnouncesArraySupport() + { + $modifier = new PropertyModifierArrayFilter(); + $this->assertTrue($modifier->hasArraySupport()); + } +} diff --git a/test/php/library/Director/PropertyModifier/PropertyModifierCombineTest.php b/test/php/library/Director/PropertyModifier/PropertyModifierCombineTest.php new file mode 100644 index 0000000..4c42dba --- /dev/null +++ b/test/php/library/Director/PropertyModifier/PropertyModifierCombineTest.php @@ -0,0 +1,51 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\PropertyModifier\PropertyModifierCombine; +use Icinga\Module\Director\Test\BaseTestCase; + +class PropertyModifierCombineTest extends BaseTestCase +{ + public function testBuildsTypicalHostServiceCombination() + { + $row = (object) array('host' => 'localhost', 'service' => 'ping'); + $modifier = new PropertyModifierCombine(); + $modifier->setSettings(array('pattern' => '${host}!${service}')); + + $this->assertEquals( + 'localhost!ping', + $modifier->setRow($row)->transform('something') + ); + } + + public function testDoesNotFailForMissingProperties() + { + $row = (object) array('host' => 'localhost'); + $modifier = new PropertyModifierCombine(); + $modifier->setSettings(array('pattern' => '${host}!${service}')); + + $this->assertEquals( + 'localhost!', + $modifier->setRow($row)->transform('something') + ); + } + + public function testDoesNotEvaluateVariablesFromDataSource() + { + $row = (object) array('host' => '${service}', 'service' => 'ping'); + $modifier = new PropertyModifierCombine(); + $modifier->setSettings(array('pattern' => '${host}!${service}')); + + $this->assertEquals( + '${service}!ping', + $modifier->setRow($row)->transform('something') + ); + } + + public function testRequiresRow() + { + $modifier = new PropertyModifierCombine(); + $this->assertTrue($modifier->requiresRow()); + } +} diff --git a/test/php/library/Director/PropertyModifier/PropertyModifierListToObjectTest.php b/test/php/library/Director/PropertyModifier/PropertyModifierListToObjectTest.php new file mode 100644 index 0000000..93d498c --- /dev/null +++ b/test/php/library/Director/PropertyModifier/PropertyModifierListToObjectTest.php @@ -0,0 +1,104 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\PropertyModifier\PropertyModifierListToObject; +use Icinga\Module\Director\Test\BaseTestCase; + +class PropertyModifierListToObjectTest extends BaseTestCase +{ + public function testConvertsAListOfArrays() + { + $this->assertEquals( + $this->getOutput(), + $this->getNewModifier()->transform($this->getInputArrays()) + ); + } + + public function testConvertsAListOfObjects() + { + $this->assertEquals( + $this->getOutput(), + $this->getNewModifier()->transform($this->getInputObjects()) + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFailsOnMissingKey() + { + $input = $this->getInputArrays(); + unset($input[0]['name']); + $this->getNewModifier()->transform($input); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFailsWithDuplicateRows() + { + $input = $this->getInputArrays(); + $input[1]['name'] = 'row1'; + $this->getNewModifier()->transform($input); + } + + public function testKeepsFirstRowOnDuplicate() + { + $input = $this->getInputArrays(); + $input[1]['name'] = 'row1'; + $modifier = $this->getNewModifier()->setSetting('on_duplicate', 'keep_first'); + $result = $modifier->transform($input); + $this->assertEquals( + (object) ['some' => 'property'], + $result->row1->props + ); + } + + public function testKeepsLastRowOnDuplicate() + { + $input = $this->getInputArrays(); + $input[1]['name'] = 'row1'; + $modifier = $this->getNewModifier()->setSetting('on_duplicate', 'keep_last'); + $result = $modifier->transform($input); + $this->assertEquals( + (object) ['other' => 'property'], + $result->row1->props + ); + } + + protected function getNewModifier() + { + $modifier = new PropertyModifierListToObject(); + $modifier->setSettings([ + 'key_property' => 'name', + 'on_duplicate' => 'fail' + ]); + + return $modifier; + } + + protected function getInputArrays() + { + return [ + ['name' => 'row1', 'props' => (object) ['some' => 'property']], + ['name' => 'row2', 'props' => (object) ['other' => 'property']], + ]; + } + + protected function getInputObjects() + { + return [ + (object) ['name' => 'row1', 'props' => (object) ['some' => 'property']], + (object) ['name' => 'row2', 'props' => (object) ['other' => 'property']], + ]; + } + + protected function getOutput() + { + return (object) [ + 'row1' => (object) ['name' => 'row1', 'props' => (object) ['some' => 'property']], + 'row2' => (object) ['name' => 'row2', 'props' => (object) ['other' => 'property']], + ]; + } +} diff --git a/test/php/library/Director/PropertyModifier/PropertyModifierParseURLTest.php b/test/php/library/Director/PropertyModifier/PropertyModifierParseURLTest.php new file mode 100644 index 0000000..a5ccb79 --- /dev/null +++ b/test/php/library/Director/PropertyModifier/PropertyModifierParseURLTest.php @@ -0,0 +1,147 @@ +<?php + +namespace Tests\Icinga\Module\Director\PropertyModifier; + +use Icinga\Module\Director\PropertyModifier\PropertyModifierParseURL; +use Icinga\Module\Director\Test\BaseTestCase; + +class PropertyModifierParseURLTest extends BaseTestCase +{ + protected static $validurl = 'https://www.icinga.org/path/file.html?foo=bar#section'; + protected static $invalidurl = 'http:///www.icinga.org/'; + + + public function testModifierDoesNotSupportArraysItself() + { + $modifier = new PropertyModifierParseURL(); + $this->assertFalse($modifier->hasArraySupport()); + } + + public function testEmptyPropertyReturnsNullOnfailureNull() + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => 'query', + 'on_failure' => 'null', + ]); + + $this->assertNull($modifier->transform('')); + } + + public function testMissingComponentReturnsNullOnfailureNull() + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => 'query', + 'on_failure' => 'null', + ]); + + $this->assertNull($modifier->transform('https://www.icinga.org/path/')); + } + + public function testMissingComponentReturnsPropertyOnfailureKeep() + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => 'query', + 'on_failure' => 'keep', + ]); + + $this->assertEquals('http://www.icinga.org/path/', $modifier->transform('http://www.icinga.org/path/')); + } + + /** + * @expectedException \Icinga\Exception\InvalidPropertyException + */ + public function testMissingComponentThrowsExceptionOnfailureFail() + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => 'query', + 'on_failure' => 'fail', + ]); + + $modifier->transform('http://www.icinga.org/path/'); + } + + + public function testInvalidUrlReturnsNullOnfailureNull() + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => 'host', + 'on_failure' => 'null', + ]); + + $this->assertNull($modifier->transform(self::$invalidurl)); + } + + public function testInvalidUrlReturnsItselfOnfailureKeep() + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => 'host', + 'on_failure' => 'keep', + ]); + + $this->assertEquals(self::$invalidurl, $modifier->transform(self::$invalidurl)); + } + + /** + * @expectedException \Icinga\Exception\InvalidPropertyException + */ + public function testInvalidUrlThrowsExceptionOnfailureFail() + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => 'host', + 'on_failure' => 'fail', + ]); + + $modifier->transform(self::$invalidurl); + } + + + /** + * @dataProvider dataURLcomponentProvider + */ + public function testSuccessfullyParse($component, $result) + { + $modifier = new PropertyModifierParseURL(); + $modifier->setSettings([ + 'url_component' => $component, + 'on_failure' => 'null', + ]); + + $this->assertEquals($result, $modifier->transform(self::$validurl)); + } + public function dataURLcomponentProvider() + { + return [ + 'scheme' => [ + 'scheme', + 'https', + ], + 'host' => [ + 'host', + 'www.icinga.org', + ], + 'port' => [ + 'port', + '', + ], + 'path' => [ + 'path', + '/path/file.html', + ], + 'query' => [ + 'query', + 'foo=bar', + ], + 'fragment' => [ + 'fragment', + 'section', + ], + ]; + } +} diff --git a/test/php/library/Director/Resolver/TemplateTreeTest.php b/test/php/library/Director/Resolver/TemplateTreeTest.php new file mode 100644 index 0000000..f44d081 --- /dev/null +++ b/test/php/library/Director/Resolver/TemplateTreeTest.php @@ -0,0 +1,259 @@ +<?php + +namespace Tests\Icinga\Module\Director\Objects; + +use Icinga\Module\Director\Db; +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Objects\IcingaService; +use Icinga\Module\Director\Resolver\TemplateTree; +use Icinga\Module\Director\Test\BaseTestCase; + +class TemplateTreeTest extends BaseTestCase +{ + protected $applyId; + + protected function prepareHosts(Db $db) + { + $o1 = IcingaHost::create([ + 'object_name' => 'o1', + 'object_type' => 'template' + ], $db); + $o2 = IcingaHost::create([ + 'object_name' => 'o2', + 'object_type' => 'template' + ], $db); + $o3 = IcingaHost::create([ + 'object_name' => 'o3', + 'object_type' => 'template' + ], $db); + $o4 = IcingaHost::create([ + 'object_name' => 'o4', + 'object_type' => 'template', + 'imports' => ['o2', 'o1'], + ], $db); + $o5 = IcingaHost::create([ + 'object_name' => 'o5', + 'object_type' => 'template', + 'imports' => ['o4'], + ], $db); + $o6 = IcingaHost::create([ + 'object_name' => 'o6', + 'object_type' => 'template', + 'imports' => ['o4', 'o2'], + ], $db); + $o7 = IcingaHost::create([ + 'object_name' => 'o7', + 'object_type' => 'object', + 'imports' => ['o4', 'o2'], + ], $db); + + $o1->store(); + $o2->store(); + $o3->store(); + $o4->store(); + $o5->store(); + $o6->store(); + $o7->store(); + + return (object) [ + 'o1' => $o1, + 'o2' => $o2, + 'o3' => $o3, + 'o4' => $o4, + 'o5' => $o5, + 'o6' => $o6, + 'o7' => $o7, + ]; + } + + public function testHostWithoutParentGivesAnEmptyArray() + { + $db = $this->getDb(); + $hosts = $this->prepareHosts($db); + $tree = new TemplateTree('host', $db); + $this->assertEquals([], $tree->getParentsFor($hosts->o2)); + $this->assertEquals([], $tree->getAncestorsFor($hosts->o2)); + $this->assertEquals([], $tree->listAncestorIdsFor($hosts->o2)); + } + + public function testSimpleInheritanceWithMultipleParentsGivesOrderedResult() + { + $db = $this->getDb(); + $hosts = $this->prepareHosts($db); + $tree = new TemplateTree('host', $db); + $this->assertArrayEqualsWithKeys([ + $hosts->o2->id => 'o2', + $hosts->o1->id => 'o1', + ], $tree->getParentsFor($hosts->o4)); + $this->assertArrayEqualsWithKeys([ + (int) $hosts->o2->id, + (int) $hosts->o1->id, + ], $tree->listParentIdsFor($hosts->o4)); + } + + public function testMultiInheritanceIsResolved() + { + $db = $this->getDb(); + $hosts = $this->prepareHosts($db); + $tree = new TemplateTree('host', $db); + $this->assertArrayEqualsWithKeys([ + $hosts->o2->id => 'o2', + $hosts->o1->id => 'o1', + $hosts->o4->id => 'o4' + ], $tree->getAncestorsFor($hosts->o5)); + $this->assertArrayEqualsWithKeys([ + (int) $hosts->o2->get('id'), + (int) $hosts->o1->getProperty('id'), + $hosts->o4->getAutoincId(), + ], $tree->listAncestorIdsFor($hosts->o5)); + } + + public function testTemplateOrderIsCorrectWhenInheritingSameTemplateMultipleTimes() + { + $db = $this->getDb(); + $hosts = $this->prepareHosts($db); + $tree = new TemplateTree('host', $db); + $this->assertArrayEqualsWithKeys([ + $hosts->o1->id => 'o1', + $hosts->o4->id => 'o4', + $hosts->o2->id => 'o2' + ], $tree->getAncestorsFor($hosts->o6)); + $this->assertArrayEqualsWithKeys([ + $hosts->o1->getAutoincId(), + $hosts->o4->getAutoincId(), + $hosts->o2->getAutoincId(), + ], $tree->listAncestorIdsFor($hosts->o6)); + } + + protected function assertArrayEqualsWithKeys($expected, $actual) + { + $message = sprintf( + 'Failed asserting that %s equals %s', + json_encode($actual), + json_encode($expected) + ); + + $this->assertTrue( + $expected === $actual, + $message + ); + } + + protected function assertSameArrayValues($expected, $actual) + { + $message = sprintf( + 'Failed asserting that %s has the same values as %s', + json_encode($actual), + json_encode($expected) + ); + + sort($expected); + sort($actual); + $this->assertTrue( + $expected === $actual, + $message + ); + } + + public function testChildrenAreResolvedCorrectlyOverMultipleLevels() + { + $db = $this->getDb(); + $o1 = IcingaService::create([ + 'object_name' => 'o1', + 'object_type' => 'template' + ], $db); + $o2 = IcingaService::create([ + 'object_name' => 'o2', + 'object_type' => 'template' + ], $db); + $o3 = IcingaService::create([ + 'object_name' => 'o3', + 'object_type' => 'template' + ], $db); + $o4 = IcingaService::create([ + 'object_name' => 'o4', + 'object_type' => 'template', + 'imports' => ['o2', 'o1'], + ], $db); + $o5 = IcingaService::create([ + 'object_name' => 'o5', + 'object_type' => 'template', + 'imports' => ['o4'], + ], $db); + $o6 = IcingaService::create([ + 'object_name' => 'o6', + 'object_type' => 'template', + 'imports' => ['o4', 'o2'], + ], $db); + $o7 = IcingaService::create([ + 'object_name' => 'o7', + 'object_type' => 'apply', + 'imports' => ['o4', 'o2'], + ], $db); + $o1->store(); + $o2->store(); + $o3->store(); + $o4->store(); + $o5->store(); + $o6->store(); + $o7->store(); + $this->applyId = (int) $o7->get('id'); + + $tree = new TemplateTree('service', $db); + $this->assertEquals([ + $o4->id => 'o4', + $o5->id => 'o5', + $o6->id => 'o6', + ], $tree->getDescendantsFor($o2)); + $this->assertSameArrayValues([ + $o4->getAutoincId(), + (int) $o5->id, + (int) $o6->getProperty('id'), + ], $tree->listDescendantIdsFor($o2)); + $this->assertEquals([ + $o5->id => 'o5', + $o6->id => 'o6', + ], $tree->getChildrenFor($o4)); + $this->assertEquals([], $tree->getChildrenFor($o5)); + } + + protected function removeHosts(Db $db) + { + $kill = array('o7', 'o6', 'o5', 'o4', 'o3', 'o2', 'o1'); + foreach ($kill as $name) { + if (IcingaHost::exists($name, $db)) { + IcingaHost::load($name, $db)->delete(); + } + } + } + + protected function removeServices(Db $db) + { + if ($this->applyId) { + $key = ['id' => $this->applyId]; + if (IcingaService::exists($key, $db)) { + IcingaService::load($key, $db)->delete(); + } + } + + $kill = array('o6', 'o5', 'o4', 'o3', 'o2', 'o1'); + foreach ($kill as $name) { + $key = [ + 'object_name' => $name, + 'object_type' => 'template', + ]; + if (IcingaService::exists($key, $db)) { + IcingaService::load($key, $db)->delete(); + } + } + } + + public function tearDown() + { + if ($this->hasDb()) { + $db = $this->getDb(); + $this->removeHosts($db); + $this->removeServices($db); + } + } +} diff --git a/test/php/library/Director/Restriction/MatchingFilterTest.php b/test/php/library/Director/Restriction/MatchingFilterTest.php new file mode 100644 index 0000000..cd1b57e --- /dev/null +++ b/test/php/library/Director/Restriction/MatchingFilterTest.php @@ -0,0 +1,56 @@ +<?php + +namespace Tests\Icinga\Module\Director\Restriction; + +use Icinga\Module\Director\Restriction\MatchingFilter; +use Icinga\Module\Director\Test\BaseTestCase; +use Icinga\User; + +class MatchingFilterTest extends BaseTestCase +{ + public function testUserWithNoRestrictionHasNoFilter() + { + $user = new User('dummy'); + $this->assertEquals( + '', + (string) MatchingFilter::forUser($user, 'some/name', 'prop') + ); + } + + public function testSimpleRestrictionRendersCorrectly() + { + $this->assertEquals( + 'prop = a*', + (string) MatchingFilter::forPatterns(['a*'], 'prop') + ); + } + + public function testMultipleRestrictionsAreCombinedWithOr() + { + $this->assertEquals( + 'prop = a* | prop = *b', + (string) MatchingFilter::forPatterns(['a*', '*b'], 'prop') + ); + } + + public function testUserWithMultipleRestrictionsWorksFine() + { + $user = new User('dummy'); + $user->setRestrictions([ + 'some/name' => ['a*', '*b'], + 'some/thing' => ['else'] + ]); + $this->assertEquals( + 'prop = a* | prop = *b', + (string) MatchingFilter::forUser($user, 'some/name', 'prop') + ); + } + + public function testSingleRestrictionAllowsForPipes() + { + $this->assertEquals( + 'prop = a* | prop = *b', + (string) MatchingFilter::forPatterns(['a*|*b'], 'prop') + ); + } +} diff --git a/test/phpunit-compat.php b/test/phpunit-compat.php new file mode 100644 index 0000000..2b1be3a --- /dev/null +++ b/test/phpunit-compat.php @@ -0,0 +1,10 @@ +<?php + +use PHPUnit\Framework\TestCase; + +/** + * @codingStandardsIgnoreStart + */ +class PHPUnit_Framework_TestCase extends TestCase +{ +} diff --git a/test/setup_vendor.sh b/test/setup_vendor.sh new file mode 100755 index 0000000..c53982f --- /dev/null +++ b/test/setup_vendor.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +set -ex + +MODULE_HOME=${MODULE_HOME:="$(dirname "$(readlink -f "$(dirname "$0")")")"} +PHP_VERSION="$(php -r 'echo phpversion();')" + +ICINGAWEB_VERSION=${ICINGAWEB_VERSION:=2.7.1} +ICINGAWEB_GITREF=${ICINGAWEB_GITREF:=} + +if [ "$PHP_VERSION" '<' 7.1.0 ]; then + PHPCS_VERSION=${PHPCS_VERSION:=3.3.2} +else + PHPCS_VERSION=${PHPCS_VERSION:=3.5.2} +fi + +if [ "$PHP_VERSION" '<' 5.6.0 ]; then + PHPUNIT_VERSION=${PHPUNIT_VERSION:=4.8} +else + PHPUNIT_VERSION=${PHPUNIT_VERSION:=5.7} +fi + +cd "${MODULE_HOME}" + +test -d vendor || mkdir vendor +cd vendor/ + +# icingaweb2 +if [ -n "$ICINGAWEB_GITREF" ]; then + icingaweb_path="icingaweb2" + test ! -L "$icingaweb_path" || rm "$icingaweb_path" + + if [ ! -d "$icingaweb_path" ]; then + git clone https://github.com/Icinga/icingaweb2.git "$icingaweb_path" + fi + + ( + set -e + cd "$icingaweb_path" + git fetch -p + git checkout -f "$ICINGAWEB_GITREF" + ) +else + icingaweb_path="icingaweb2-${ICINGAWEB_VERSION}" + if [ ! -e "${icingaweb_path}".tar.gz ]; then + wget -O "${icingaweb_path}".tar.gz https://github.com/Icinga/icingaweb2/archive/v"${ICINGAWEB_VERSION}".tar.gz + fi + if [ ! -d "${icingaweb_path}" ]; then + tar xf "${icingaweb_path}".tar.gz + fi + + rm -f icingaweb2 + ln -svf "${icingaweb_path}" icingaweb2 +fi +ln -svf "${icingaweb_path}"/library/Icinga Icinga +ln -svf "${icingaweb_path}"/library/vendor/Zend Zend + +# phpunit +phpunit_path="phpunit-${PHPUNIT_VERSION}" +if [ ! -e "${phpunit_path}".phar ]; then + wget -O "${phpunit_path}".phar https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar +fi +ln -svf "${phpunit_path}".phar phpunit.phar + +# phpcs +phpcs_path="phpcs-${PHPCS_VERSION}" +if [ ! -e "${phpcs_path}".phar ]; then + wget -O "${phpcs_path}".phar \ + https://github.com/squizlabs/PHP_CodeSniffer/releases/download/${PHPCS_VERSION}/phpcs.phar +fi +ln -svf "${phpcs_path}".phar phpcs.phar diff --git a/test/travis-prepare.sh b/test/travis-prepare.sh new file mode 100755 index 0000000..7a303e8 --- /dev/null +++ b/test/travis-prepare.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -ex + +: "${DIRECTOR_TESTDB:=director_test}" + +psql_cmd() { + psql -U postgres ${DIRECTOR_TESTDB} -q -c "$@" +} + +if [ "$DB" = mysql ]; then + mysql -u root -e "DROP DATABASE IF EXISTS ${DIRECTOR_TESTDB}; CREATE DATABASE ${DIRECTOR_TESTDB};" +elif [ "$DB" = pgsql ]; then + : "${DIRECTOR_TESTDB_USER:=director_test}" + + psql -U postgres postgres -q -c "DROP DATABASE IF EXISTS ${DIRECTOR_TESTDB};" + psql -U postgres postgres -q -c "CREATE DATABASE ${DIRECTOR_TESTDB} WITH ENCODING 'UTF8';" + psql_cmd "CREATE USER ${DIRECTOR_TESTDB_USER} WITH PASSWORD 'testing';" + psql_cmd "GRANT ALL PRIVILEGES ON DATABASE ${DIRECTOR_TESTDB} TO ${DIRECTOR_TESTDB_USER};" + psql_cmd "CREATE EXTENSION pgcrypto;" +else + echo "Unknown database set in environment!" >&2 + env + exit 1 +fi |