summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/bootstrap.php16
-rw-r--r--test/config/authentication.ini0
-rw-r--r--test/config/config.ini0
-rw-r--r--test/config/resources.ini13
-rw-r--r--test/php/library/Director/Application/DependencyTest.php72
-rw-r--r--test/php/library/Director/Application/FiltersWorkAsExpectedTest.php15
-rw-r--r--test/php/library/Director/Application/MemoryLimitTest.php67
-rw-r--r--test/php/library/Director/CustomVariable/CustomVariablesTest.php79
-rw-r--r--test/php/library/Director/Data/AssignFilterHelperTest.php86
-rw-r--r--test/php/library/Director/Data/RecursiveUtf8ValidatorTest.php45
-rw-r--r--test/php/library/Director/IcingaConfig/AssignRendererTest.php126
-rw-r--r--test/php/library/Director/IcingaConfig/ExtensibleSetTest.php162
-rw-r--r--test/php/library/Director/IcingaConfig/IcingaConfigHelperTest.php130
-rw-r--r--test/php/library/Director/IcingaConfig/StateFilterTest.php171
-rw-r--r--test/php/library/Director/IcingaConfig/rendered/dict1.out6
-rw-r--r--test/php/library/Director/Import/HostSyncTest.php250
-rw-r--r--test/php/library/Director/Import/ImportSourceRestApiTest.php29
-rw-r--r--test/php/library/Director/Import/SyncUtilsTest.php108
-rw-r--r--test/php/library/Director/Objects/HostApplyMatchesTest.php93
-rw-r--r--test/php/library/Director/Objects/HostGroupMembershipResolverTest.php353
-rw-r--r--test/php/library/Director/Objects/IcingaCommandTest.php216
-rw-r--r--test/php/library/Director/Objects/IcingaHostTest.php771
-rw-r--r--test/php/library/Director/Objects/IcingaNotificationTest.php248
-rw-r--r--test/php/library/Director/Objects/IcingaServiceSetTest.php183
-rw-r--r--test/php/library/Director/Objects/IcingaServiceTest.php293
-rw-r--r--test/php/library/Director/Objects/IcingaTemplateResolverTest.php158
-rw-r--r--test/php/library/Director/Objects/IcingaTimePeriodTest.php184
-rw-r--r--test/php/library/Director/Objects/rendered/command1.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command2.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command3.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command4.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command5.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command6.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command7.out9
-rw-r--r--test/php/library/Director/Objects/rendered/host1.out12
-rw-r--r--test/php/library/Director/Objects/rendered/host2.out17
-rw-r--r--test/php/library/Director/Objects/rendered/host3.out14
-rw-r--r--test/php/library/Director/Objects/rendered/notification1.out4
-rw-r--r--test/php/library/Director/Objects/rendered/service1.out14
-rw-r--r--test/php/library/Director/Objects/rendered/service2.out16
-rw-r--r--test/php/library/Director/Objects/rendered/service3.out15
-rw-r--r--test/php/library/Director/Objects/rendered/service4.out13
-rw-r--r--test/php/library/Director/Objects/rendered/service5.out14
-rw-r--r--test/php/library/Director/Objects/rendered/service6.out15
-rw-r--r--test/php/library/Director/Objects/rendered/service7.out14
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierArrayElementByPositionTest.php143
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierArrayFilterTest.php120
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierCombineTest.php51
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierListToObjectTest.php104
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierParseURLTest.php147
-rw-r--r--test/php/library/Director/Resolver/TemplateTreeTest.php259
-rw-r--r--test/php/library/Director/Restriction/MatchingFilterTest.php56
-rw-r--r--test/phpunit-compat.php10
-rwxr-xr-xtest/setup_vendor.sh71
-rwxr-xr-xtest/travis-prepare.sh25
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