summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:15:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:15:40 +0000
commitb7fd908d538ed19fe41f03c0a3f93351d8da64e9 (patch)
tree46e14f318948cd4f5d7e874f83e7dfcc5d42fc64 /test
parentInitial commit. (diff)
downloadicingaweb2-module-businessprocess-b7fd908d538ed19fe41f03c0a3f93351d8da64e9.tar.xz
icingaweb2-module-businessprocess-b7fd908d538ed19fe41f03c0a3f93351d8da64e9.zip
Adding upstream version 2.5.0.upstream/2.5.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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/modules/businessprocess/processes/also-with-semicolons.conf8
-rw-r--r--test/config/modules/businessprocess/processes/broken_wrong-operator.conf1
-rw-r--r--test/config/modules/businessprocess/processes/combined.conf1
-rw-r--r--test/config/modules/businessprocess/processes/simple_with-header.conf13
-rw-r--r--test/config/modules/businessprocess/processes/simple_without-header.conf6
-rw-r--r--test/config/modules/businessprocess/processes/with-semicolons.conf14
-rw-r--r--test/php/library/Businessprocess/BpConfigTest.php49
-rw-r--r--test/php/library/Businessprocess/BpNodeTest.php39
-rw-r--r--test/php/library/Businessprocess/HostNodeTest.php63
-rw-r--r--test/php/library/Businessprocess/MetadataTest.php32
-rw-r--r--test/php/library/Businessprocess/Operators/AndOperatorTest.php214
-rw-r--r--test/php/library/Businessprocess/Operators/DegradedOperatorTest.php159
-rw-r--r--test/php/library/Businessprocess/Operators/MinOperatorTest.php174
-rw-r--r--test/php/library/Businessprocess/Operators/NotOperatorTest.php151
-rw-r--r--test/php/library/Businessprocess/Operators/OrOperatorTest.php116
-rw-r--r--test/php/library/Businessprocess/ServiceNodeTest.php56
-rw-r--r--test/php/library/Businessprocess/SimulationTest.php47
-rw-r--r--test/php/library/Businessprocess/Storage/LegacyStorageTest.php175
-rw-r--r--test/php/library/Businessprocess/Web/Component/TabsTest.php17
-rw-r--r--test/phpunit-compat.php10
-rwxr-xr-xtest/setup_vendor.sh82
24 files changed, 1443 insertions, 0 deletions
diff --git a/test/bootstrap.php b/test/bootstrap.php
new file mode 100644
index 0000000..e12df22
--- /dev/null
+++ b/test/bootstrap.php
@@ -0,0 +1,16 @@
+<?php
+
+use Icinga\Module\Businessprocess\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/Businessprocess/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/modules/businessprocess/processes/also-with-semicolons.conf b/test/config/modules/businessprocess/processes/also-with-semicolons.conf
new file mode 100644
index 0000000..a023aaf
--- /dev/null
+++ b/test/config/modules/businessprocess/processes/also-with-semicolons.conf
@@ -0,0 +1,8 @@
+############################################
+#
+# Title: Also With Semicolons
+#
+############################################
+
+b\;ar =
+display 1;b\;ar;Bar
diff --git a/test/config/modules/businessprocess/processes/broken_wrong-operator.conf b/test/config/modules/businessprocess/processes/broken_wrong-operator.conf
new file mode 100644
index 0000000..9a58f23
--- /dev/null
+++ b/test/config/modules/businessprocess/processes/broken_wrong-operator.conf
@@ -0,0 +1 @@
+hostsAnd = host1;Hoststatus + host2;Hoststatus \ No newline at end of file
diff --git a/test/config/modules/businessprocess/processes/combined.conf b/test/config/modules/businessprocess/processes/combined.conf
new file mode 100644
index 0000000..3b0fc5d
--- /dev/null
+++ b/test/config/modules/businessprocess/processes/combined.conf
@@ -0,0 +1 @@
+all = @simple_with-header:top & @simple_without-header:minTwo & @simple_with-header:minTwo \ No newline at end of file
diff --git a/test/config/modules/businessprocess/processes/simple_with-header.conf b/test/config/modules/businessprocess/processes/simple_with-header.conf
new file mode 100644
index 0000000..802fbb2
--- /dev/null
+++ b/test/config/modules/businessprocess/processes/simple_with-header.conf
@@ -0,0 +1,13 @@
+############################################
+#
+# Title: Simple with header
+#
+############################################
+
+hostsAnd = host1;Hoststatus & host2;Hoststatus
+servicesOr = host1;ping | host2;ping | host3;ping
+singleHost = host1;Hoststatus
+minTwo = 2 of: hostsAnd + servicesOr + singleHost
+top = minTwo & hostsAnd & servicesOr
+display 1;top;Top Node
+info_url top;https://top.example.com/
diff --git a/test/config/modules/businessprocess/processes/simple_without-header.conf b/test/config/modules/businessprocess/processes/simple_without-header.conf
new file mode 100644
index 0000000..7d4efc6
--- /dev/null
+++ b/test/config/modules/businessprocess/processes/simple_without-header.conf
@@ -0,0 +1,6 @@
+hostsAnd = host1;Hoststatus & host2;Hoststatus
+servicesOr = host1;ping | host2;ping | host3;ping
+singleHost = host1;Hoststatus
+minTwo = 2 of: hostsAnd + servicesOr + singleHost
+top = minTwo & hostsAnd & servicesOr
+display 1;top;Top Node \ No newline at end of file
diff --git a/test/config/modules/businessprocess/processes/with-semicolons.conf b/test/config/modules/businessprocess/processes/with-semicolons.conf
new file mode 100644
index 0000000..310d473
--- /dev/null
+++ b/test/config/modules/businessprocess/processes/with-semicolons.conf
@@ -0,0 +1,14 @@
+############################################
+#
+# Title: With Semicolons
+#
+############################################
+
+hostsAnd = host\;1;Hoststatus & host2;Hoststatus
+servicesOr = host\;1;pi;ng | host2;ping | host3;ping
+singleHost = host\;1;Hoststatus & to\;p & @also-with-semicolons:b\;ar
+to\;p = hostsAnd & servicesOr & singleHost
+display 1;to\;p;Top Node
+info_url to\;p;https://top.example.com/
+no\;alias =
+display 1;no\;alias;no;alias
diff --git a/test/php/library/Businessprocess/BpConfigTest.php b/test/php/library/Businessprocess/BpConfigTest.php
new file mode 100644
index 0000000..f42c58c
--- /dev/null
+++ b/test/php/library/Businessprocess/BpConfigTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+
+class BpConfigTest extends BaseTestCase
+{
+ public function testJoinNodeName()
+ {
+ $this->assertSame(
+ 'foo;bar',
+ BpConfig::joinNodeName('foo', 'bar')
+ );
+ $this->assertSame(
+ 'foo\;bar',
+ BpConfig::joinNodeName('foo;bar')
+ );
+ $this->assertSame(
+ 'foo\;bar;baroof',
+ BpConfig::joinNodeName('foo;bar', 'baroof')
+ );
+ $this->assertSame(
+ 'foo\;bar;bar;oof',
+ BpConfig::joinNodeName('foo;bar', 'bar;oof')
+ );
+ }
+
+ public function testSplitNodeName()
+ {
+ $this->assertSame(
+ ['foo', 'bar'],
+ BpConfig::splitNodeName('foo;bar')
+ );
+ $this->assertSame(
+ ['foo;bar', null],
+ BpConfig::splitNodeName('foo\;bar')
+ );
+ $this->assertSame(
+ ['foo;bar', 'baroof'],
+ BpConfig::splitNodeName('foo\;bar;baroof')
+ );
+ $this->assertSame(
+ ['foo;bar', 'bar;oof'],
+ BpConfig::splitNodeName('foo\;bar;bar;oof')
+ );
+ }
+}
diff --git a/test/php/library/Businessprocess/BpNodeTest.php b/test/php/library/Businessprocess/BpNodeTest.php
new file mode 100644
index 0000000..c3da723
--- /dev/null
+++ b/test/php/library/Businessprocess/BpNodeTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess;
+
+use Icinga\Module\Businessprocess\BpNode;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+
+class BpNodeTest extends BaseTestCase
+{
+ public function testThrowsNestingErrorWhenCheckedForLoops()
+ {
+ $this->expectException(\Icinga\Module\Businessprocess\Exception\NestingError::class);
+
+ /** @var BpNode $bpNode */
+ $bpNode = $this->makeLoop()->getNode('d');
+ $bpNode->checkForLoops();
+ }
+
+ public function testNestingErrorReportsFullLoop()
+ {
+ $this->expectException(\Icinga\Module\Businessprocess\Exception\NestingError::class);
+ $this->expectExceptionMessage('d -> a -> b -> c -> a');
+
+ /** @var BpNode $bpNode */
+ $bpNode = $this->makeLoop()->getNode('d');
+ $bpNode->checkForLoops();
+ }
+
+ public function testStateForALoopGivesUnknown()
+ {
+ $loop = $this->makeLoop();
+ /** @var BpNode $bpNode */
+ $bpNode = $loop->getNode('d');
+ $this->assertEquals(
+ 'UNKNOWN',
+ $bpNode->getStateName()
+ );
+ }
+}
diff --git a/test/php/library/Businessprocess/HostNodeTest.php b/test/php/library/Businessprocess/HostNodeTest.php
new file mode 100644
index 0000000..ef4155d
--- /dev/null
+++ b/test/php/library/Businessprocess/HostNodeTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\HostNode;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+
+class HostNodeTest extends BaseTestCase
+{
+ public function testReturnsCorrectHostName()
+ {
+ $this->assertEquals(
+ 'localhost',
+ $this->localhost()->getHostname()
+ );
+ }
+
+ public function testReturnsCorrectIdentifierWhenCastedToString()
+ {
+ $this->assertEquals(
+ 'localhost;Hoststatus',
+ $this->localhost()->getName()
+ );
+ }
+
+ public function testReturnsCorrectAlias()
+ {
+ $this->assertEquals(
+ 'localhost',
+ $this->localhost()->getAlias()
+ );
+ }
+
+ public function testRendersCorrectLink()
+ {
+ $this->assertEquals(
+ '<a href="/icingaweb2/businessprocess/host/show?host=localhost">'
+ . 'localhost</a>',
+ $this->localhost()->getLink()->render()
+ );
+ }
+
+ public function testSettingAnInvalidStateFails()
+ {
+ $this->expectException(\Icinga\Exception\ProgrammingError::class);
+ $bp = new BpConfig();
+ $host = $bp->createHost('localhost')->setState(98);
+ $bp->createBp('p')->addChild($host)->getState();
+ }
+
+ /**
+ * @return HostNode
+ */
+ protected function localhost()
+ {
+ $bp = new BpConfig();
+ return (new HostNode((object) array(
+ 'hostname' => 'localhost',
+ 'state' => 0,
+ )))->setBpConfig($bp)->setAlias('localhost');
+ }
+}
diff --git a/test/php/library/Businessprocess/MetadataTest.php b/test/php/library/Businessprocess/MetadataTest.php
new file mode 100644
index 0000000..765caf8
--- /dev/null
+++ b/test/php/library/Businessprocess/MetadataTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess;
+
+use Icinga\Module\Businessprocess\Metadata;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+
+class MetadataTest extends BaseTestCase
+{
+ public function testDetectsMatchingPrefixes()
+ {
+ $meta = new Metadata('matchme');
+ $this->assertFalse(
+ $meta->nameIsPrefixedWithOneOf(array())
+ );
+ $this->assertFalse(
+ $meta->nameIsPrefixedWithOneOf(array('matchr', 'atchme'))
+ );
+ $this->assertTrue(
+ $meta->nameIsPrefixedWithOneOf(array('not', 'mat', 'yes'))
+ );
+ $this->assertTrue(
+ $meta->nameIsPrefixedWithOneOf(array('m'))
+ );
+ $this->assertTrue(
+ $meta->nameIsPrefixedWithOneOf(array('matchme'))
+ );
+ $this->assertFalse(
+ $meta->nameIsPrefixedWithOneOf(array('matchmenot'))
+ );
+ }
+}
diff --git a/test/php/library/Businessprocess/Operators/AndOperatorTest.php b/test/php/library/Businessprocess/Operators/AndOperatorTest.php
new file mode 100644
index 0000000..9e87cf1
--- /dev/null
+++ b/test/php/library/Businessprocess/Operators/AndOperatorTest.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess\Operator;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+
+class AndOperatorTest extends BaseTestCase
+{
+ public function testTheOperatorCanBeParsed()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expressions = array(
+ 'a = b;c',
+ 'a = b;c & c;d & d;e',
+ );
+
+ foreach ($expressions as $expression) {
+ $this->assertInstanceOf(
+ 'Icinga\\Module\\Businessprocess\\BpConfig',
+ $storage->loadFromString('dummy', $expression)
+ );
+ }
+ }
+
+ public function testThreeTimesCriticalIsCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 2);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoTimesCriticalAndOkIsCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 0);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testCriticalAndWarningAndOkIsCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testUnknownAndWarningAndOkIsUnknown()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 3);
+
+ $this->assertEquals(
+ 'UNKNOWN',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoTimesWarningAndOkIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testThreeTimesOkIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 0);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testSimpleAndOperationWorksCorrectly()
+ {
+ $bp = new BpConfig();
+ $bp->throwErrors();
+ $host = $bp->createHost('localhost')->setState(1);
+ $service = $bp->createService('localhost', 'ping')->setState(1);
+ $p = $bp->createBp('p');
+ $p->addChild($host);
+ $p->addChild($service);
+
+ $this->assertEquals(
+ 'DOWN',
+ $host->getStateName()
+ );
+
+ $this->assertEquals(
+ 'WARNING',
+ $service->getStateName()
+ );
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $p->getStateName()
+ );
+ }
+
+ public function testSimpleOrOperationWorksCorrectly()
+ {
+ $bp = new BpConfig();
+ $bp->throwErrors();
+ $host = $bp->createHost('localhost')->setState(1);
+ $service = $bp->createService('localhost', 'ping')->setState(1);
+ $p = $bp->createBp('p', '|');
+ $p->addChild($host);
+ $p->addChild($service);
+
+ $this->assertEquals('DOWN', $host->getStateName());
+ $this->assertEquals('WARNING', $service->getStateName());
+ $this->assertEquals('WARNING', $p->getStateName());
+ }
+
+ public function testPendingIsAccepted()
+ {
+ $bp = new BpConfig();
+ $host = $bp->createHost('localhost')->setState(99);
+ $service = $bp->createService('localhost', 'ping')->setState(99);
+ $p = $bp->createBp('p')
+ ->addChild($host)
+ ->addChild($service);
+
+ $this->assertEquals(
+ 'PENDING',
+ $p->getStateName()
+ );
+ }
+
+ public function testWhetherWarningIsWorseThanPending()
+ {
+ $bp = new BpConfig();
+ $host = $bp->createHost('localhost')->setState(99);
+ $service = $bp->createService('localhost', 'ping')->setState(1);
+ $p = $bp->createBp('p')
+ ->addChild($host)
+ ->addChild($service);
+
+ $this->assertEquals(
+ 'WARNING',
+ $p->getStateName()
+ );
+ }
+
+ public function testPendingIsWorseThanUpOrOk()
+ {
+ $bp = new BpConfig();
+ $host = $bp->createHost('localhost')->setState(99);
+ $service = $bp->createService('localhost', 'ping')->setState(0);
+ $p = $bp->createBp('p')
+ ->addChild($host)
+ ->addChild($service);
+
+ $this->assertEquals(
+ 'PENDING',
+ $p->getStateName()
+ );
+
+ $p->clearState();
+ $host->setState(0);
+ $service->setState(99);
+
+ $this->assertEquals(
+ 'PENDING',
+ $p->getStateName()
+ );
+ }
+
+ /**
+ * @return BpConfig
+ */
+ protected function getBp()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expression = 'a = b;c & c;d & d;e';
+ $bp = $storage->loadFromString('dummy', $expression);
+ $bp->createBp('b');
+ $bp->createBp('c');
+ $bp->createBp('d');
+
+ return $bp;
+ }
+}
diff --git a/test/php/library/Businessprocess/Operators/DegradedOperatorTest.php b/test/php/library/Businessprocess/Operators/DegradedOperatorTest.php
new file mode 100644
index 0000000..72ed5e5
--- /dev/null
+++ b/test/php/library/Businessprocess/Operators/DegradedOperatorTest.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess\Operator;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+
+class DegradedOperatorTest extends BaseTestCase
+{
+ public function testDegradedOperatorCanBeParsed()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expressions = [
+ 'a = b;c',
+ 'a = b;c % c;d % d;e',
+ ];
+
+ foreach ($expressions as $expression) {
+ $this->assertInstanceOf(
+ 'Icinga\\Module\\Businessprocess\\BpConfig',
+ $storage->loadFromString('dummy', $expression)
+ );
+ }
+ }
+
+ public function testThreeTimesCriticalIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 2);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoTimesCriticalAndOkIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 0);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testCriticalAndWarningAndOkIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testUnknownAndWarningAndOkIsUnknown()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 3);
+
+ $this->assertEquals(
+ 'UNKNOWN',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoTimesWarningAndOkIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testUnknownAndWarningAndCriticalIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 3);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testThreeTimesOkIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 0);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testSimpleDegOperationWorksCorrectly()
+ {
+ $bp = new BpConfig();
+ $bp->throwErrors();
+ $host = $bp->createHost('localhost')->setState(0);
+ $service = $bp->createService('localhost', 'ping')->setState(2);
+ $p = $bp->createBp('p');
+ $p->setOperator('%');
+ $p->addChild($host);
+ $p->addChild($service);
+
+ $this->assertEquals(
+ 'UP',
+ $host->getStateName()
+ );
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $service->getStateName()
+ );
+
+ $this->assertEquals(
+ 'WARNING',
+ $p->getStateName()
+ );
+ }
+
+ /**
+ * @return BpConfig
+ */
+ protected function getBp()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expression = 'a = b;c % c;d % d;e';
+ $bp = $storage->loadFromString('dummy', $expression);
+ $bp->createBp('b');
+ $bp->createBp('c');
+ $bp->createBp('d');
+
+ return $bp;
+ }
+}
diff --git a/test/php/library/Businessprocess/Operators/MinOperatorTest.php b/test/php/library/Businessprocess/Operators/MinOperatorTest.php
new file mode 100644
index 0000000..986589a
--- /dev/null
+++ b/test/php/library/Businessprocess/Operators/MinOperatorTest.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess\Operator;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+
+class MinOperatorTest extends BaseTestCase
+{
+ public function testTheOperatorCanBeParsed()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expressions = array(
+ 'a = 1 of: b;c',
+ 'a = 2 of: b;c + c;d + d;e',
+ );
+ $this->getName();
+ foreach ($expressions as $expression) {
+ $this->assertInstanceOf(
+ 'Icinga\\Module\\Businessprocess\\BpConfig',
+ $storage->loadFromString('dummy', $expression)
+ );
+ }
+ }
+ public function testTwoOfThreeTimesCriticalAreAtLeastCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 2);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoOfTwoTimesCriticalAndUnknownAreAtLeastCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 3);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoOfCriticalAndWarningAndOkAreAtLeastCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoOfUnknownAndWarningAndCriticalAreAtLeastCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 3);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoOfTwoTimesWarningAndUnknownAreAtLeastUnknown()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 3);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'UNKNOWN',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoOfThreeTimesOkAreAtLeastOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 0);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTenWithAllOk()
+ {
+ $bp = $this->getBp(10, 9, 0);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTenWithOnlyTwoCritical()
+ {
+ $bp = $this->getBp(10, 8, 0);
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 2);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTenWithThreeCritical()
+ {
+ $bp = $this->getBp(10, 8, 0);
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 2);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTenWithThreeWarning()
+ {
+ $bp = $this->getBp(10, 8, 0);
+ $bp->setNodeState('b;c', 1);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ /**
+ * @return BpConfig
+ */
+ protected function getBp($count = 3, $min = 2, $defaultState = null)
+ {
+ $names = array();
+ $a = 97;
+ for ($i = 1; $i <= $count; $i++) {
+ $names[] = chr($a + $i) . ';' . chr($a + $i + 1);
+ }
+
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expression = sprintf('a = %d of: %s', $min, join(' + ', $names));
+ $bp = $storage->loadFromString('dummy', $expression);
+ foreach ($names as $n) {
+ if ($defaultState !== null) {
+ $bp->setNodeState($n, $defaultState);
+ }
+ }
+
+ return $bp;
+ }
+}
diff --git a/test/php/library/Businessprocess/Operators/NotOperatorTest.php b/test/php/library/Businessprocess/Operators/NotOperatorTest.php
new file mode 100644
index 0000000..fb62545
--- /dev/null
+++ b/test/php/library/Businessprocess/Operators/NotOperatorTest.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess\Operator;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+
+class NotOperatorTest extends BaseTestCase
+{
+ public function testNegationOperatorsCanBeParsed()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expressions = array(
+ 'a = !b;c',
+ 'a = ! b;c',
+ 'a = b;c ! c;d ! d;e',
+ 'a = ! b;c ! c;d ! d;e !',
+ );
+
+ foreach ($expressions as $expression) {
+ $this->assertInstanceOf(
+ 'Icinga\\Module\\Businessprocess\\BpConfig',
+ $storage->loadFromString('dummy', $expression)
+ );
+ }
+ }
+
+ public function testASimpleNegationGivesTheCorrectResult()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expression = 'a = !b;c';
+ $bp = $storage->loadFromString('dummy', $expression);
+ $a = $bp->getNode('a');
+ $b = $bp->getNode('b;c')->setState(3);
+ $this->assertEquals(
+ 'OK',
+ $a->getStateName()
+ );
+
+ $a->clearState();
+ $b->setState(0);
+ $this->assertEquals(
+ 'CRITICAL',
+ $a->getStateName()
+ );
+ }
+
+ public function testThreeTimesCriticalIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 2);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testThreeTimesUnknownIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 3);
+ $bp->setNodeState('c;d', 3);
+ $bp->setNodeState('d;e', 3);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testThreeTimesWarningIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 1);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testThreeTimesOkIsCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 0);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testNotOkAndWarningAndCriticalIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testNotWarningAndUnknownAndCriticalIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 3);
+ $bp->setNodeState('c;d', 2);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testNotTwoTimesWarningAndOkIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ /**
+ * @return BpConfig
+ */
+ protected function getBp()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expression = 'a = ! b;c ! c;d ! d;e';
+ $bp = $storage->loadFromString('dummy', $expression);
+
+ return $bp;
+ }
+}
diff --git a/test/php/library/Businessprocess/Operators/OrOperatorTest.php b/test/php/library/Businessprocess/Operators/OrOperatorTest.php
new file mode 100644
index 0000000..02043d0
--- /dev/null
+++ b/test/php/library/Businessprocess/Operators/OrOperatorTest.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess\Operator;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+
+class OrOperatorTest extends BaseTestCase
+{
+ public function testTheOperatorCanBeParsed()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expressions = array(
+ 'a = b;c',
+ 'a = b;c | c;d | d;e',
+ );
+
+ foreach ($expressions as $expression) {
+ $this->assertInstanceOf(
+ 'Icinga\\Module\\Businessprocess\\BpConfig',
+ $storage->loadFromString('dummy', $expression)
+ );
+ }
+ }
+
+ public function testThreeTimesCriticalIsCritical()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 2);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'CRITICAL',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoTimesCriticalOrUnknownIsUnknown()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 3);
+ $bp->setNodeState('d;e', 2);
+
+ $this->assertEquals(
+ 'UNKNOWN',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testCriticalOrWarningOrOkIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 0);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testUnknownOrWarningOrCriticalIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 2);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 3);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testTwoTimesWarningAndOkIsOk()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 0);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'OK',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ public function testThreeTimesWarningIsWarning()
+ {
+ $bp = $this->getBp();
+ $bp->setNodeState('b;c', 1);
+ $bp->setNodeState('c;d', 1);
+ $bp->setNodeState('d;e', 1);
+
+ $this->assertEquals(
+ 'WARNING',
+ $bp->getNode('a')->getStateName()
+ );
+ }
+
+ /**
+ * @return BpConfig
+ */
+ protected function getBp()
+ {
+ $storage = new LegacyStorage($this->emptyConfigSection());
+ $expression = 'a = b;c | c;d | d;e';
+ $bp = $storage->loadFromString('dummy', $expression);
+
+ return $bp;
+ }
+}
diff --git a/test/php/library/Businessprocess/ServiceNodeTest.php b/test/php/library/Businessprocess/ServiceNodeTest.php
new file mode 100644
index 0000000..62c1605
--- /dev/null
+++ b/test/php/library/Businessprocess/ServiceNodeTest.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\ServiceNode;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+
+class ServiceNodeTest extends BaseTestCase
+{
+ public function testReturnsCorrectHostName()
+ {
+ $this->assertEquals(
+ 'localhost',
+ $this->pingOnLocalhost()->getHostname()
+ );
+ }
+
+ public function testReturnsCorrectServiceDescription()
+ {
+ $this->assertEquals(
+ 'ping <> pong',
+ $this->pingOnLocalhost()->getServiceDescription()
+ );
+ }
+
+ public function testReturnsCorrectAlias()
+ {
+ $this->assertEquals(
+ 'ping <> pong on localhost',
+ $this->pingOnLocalhost()->getAlias()
+ );
+ }
+
+ public function testRendersCorrectLink()
+ {
+ $this->assertEquals(
+ '<a href="/icingaweb2/businessprocess/service/show?host=localhost&amp;service=ping%20%3C%3E%20pong">'
+ . 'ping &lt;&gt; pong on localhost</a>',
+ $this->pingOnLocalhost()->getLink()->render()
+ );
+ }
+
+ /**
+ * @return ServiceNode
+ */
+ protected function pingOnLocalhost()
+ {
+ $bp = new BpConfig();
+ return (new ServiceNode((object) array(
+ 'hostname' => 'localhost',
+ 'service' => 'ping <> pong',
+ 'state' => 0,
+ )))->setBpConfig($bp)->setHostAlias('localhost')->setAlias('ping <> pong');
+ }
+}
diff --git a/test/php/library/Businessprocess/SimulationTest.php b/test/php/library/Businessprocess/SimulationTest.php
new file mode 100644
index 0000000..aefeb91
--- /dev/null
+++ b/test/php/library/Businessprocess/SimulationTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess;
+
+use Icinga\Module\Businessprocess\Simulation;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+
+class SimulationTest extends BaseTestCase
+{
+ public function testSimulationInstantiation()
+ {
+ $class = 'Icinga\\Module\\Businessprocess\\Simulation';
+ $this->assertInstanceOf(
+ $class,
+ Simulation::create()
+ );
+ }
+
+ public function testAppliedSimulation()
+ {
+ $data = (object) array(
+ 'state' => 0,
+ 'acknowledged' => false,
+ 'in_downtime' => false
+ );
+ $config = $this->makeInstance()->loadProcess('simple_with-header');
+ $simulation = Simulation::create(array(
+ 'host1;Hoststatus' => $data
+ ));
+ $parent = $config->getBpNode('singleHost');
+
+ $config->applySimulation($simulation);
+ $this->assertEquals(
+ 'OK',
+ $parent->getStateName()
+ );
+
+ $parent->clearState();
+ $data->state = 1;
+ $simulation->set('host1;Hoststatus', $data);
+ $config->applySimulation($simulation);
+ $this->assertEquals(
+ 'CRITICAL',
+ $parent->getStateName()
+ );
+ }
+}
diff --git a/test/php/library/Businessprocess/Storage/LegacyStorageTest.php b/test/php/library/Businessprocess/Storage/LegacyStorageTest.php
new file mode 100644
index 0000000..75bfcd5
--- /dev/null
+++ b/test/php/library/Businessprocess/Storage/LegacyStorageTest.php
@@ -0,0 +1,175 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess\Storage;
+
+use Icinga\Module\Businessprocess\BpNode;
+use Icinga\Module\Businessprocess\ImportedNode;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+
+class LegacyStorageTest extends BaseTestCase
+{
+ private $processClass = 'Icinga\\Module\\Businessprocess\\BpConfig';
+
+ public function testCanBeInstantiatedWithAnEmptyConfigSection()
+ {
+ $baseClass = 'Icinga\\Module\\Businessprocess\\Storage\\LegacyStorage';
+ $this->assertInstanceOf(
+ $baseClass,
+ new LegacyStorage($this->emptyConfigSection())
+ );
+ }
+
+ public function testDefaultConfigDirIsDiscoveredCorrectly()
+ {
+ $this->assertEquals(
+ $this->getTestsBaseDir('config/modules/businessprocess/processes'),
+ $this->makeInstance()->getConfigDir()
+ );
+ }
+
+ public function testAllAvailableProcessesAreListed()
+ {
+ $keys = array_keys($this->makeInstance()->listProcesses());
+ $this->assertEquals(
+ array(
+ 'also-with-semicolons',
+ 'broken_wrong-operator',
+ 'combined',
+ 'simple_with-header',
+ 'simple_without-header',
+ 'with-semicolons'
+ ),
+ $keys
+ );
+ }
+
+ public function testHeaderTitlesAreRespectedInProcessList()
+ {
+ $keys = array_values($this->makeInstance()->listProcesses());
+ $this->assertEquals(
+ array(
+ 'Also With Semicolons (also-with-semicolons)',
+ 'broken_wrong-operator',
+ 'combined',
+ 'Simple with header (simple_with-header)',
+ 'simple_without-header',
+ 'With Semicolons (with-semicolons)'
+ ),
+ $keys
+ );
+ }
+
+ public function testProcessFilenameIsReturned()
+ {
+ $this->assertEquals(
+ $this->getTestsBaseDir('config/modules/businessprocess/processes/simple_with-header.conf'),
+ $this->makeInstance()->getFilename('simple_with-header')
+ );
+ }
+
+ public function testAnExistingProcessExists()
+ {
+ $this->assertTrue(
+ $this->makeInstance()->hasProcess('simple_with-header')
+ );
+ }
+
+ public function testAMissingProcessIsMissing()
+ {
+ $this->assertFalse(
+ $this->makeInstance()->hasProcess('simple_with-headerx')
+ );
+ }
+
+ public function testAValidProcessCanBeLoaded()
+ {
+ $this->assertInstanceOf(
+ $this->processClass,
+ $this->makeInstance()->loadProcess('simple_with-header')
+ );
+ }
+
+ public function testProcessConfigCanBeLoadedFromAString()
+ {
+ $this->assertInstanceOf(
+ $this->processClass,
+ $this->makeInstance()->loadFromString('dummy', 'a = Host1;ping & Host2;ping')
+ );
+ }
+
+ public function testFullProcessSourceCanBeFetched()
+ {
+ $this->assertEquals(
+ file_get_contents(
+ $this->getTestsBaseDir(
+ 'config/modules/businessprocess/processes/simple_with-header.conf'
+ )
+ ),
+ $this->makeInstance()->getSource('simple_with-header')
+ );
+ }
+
+ public function testTitleCanBeReadFromConfig()
+ {
+ $this->assertEquals(
+ 'Simple with header',
+ $this->makeInstance()->loadProcess('simple_with-header')->getMetadata()->get('Title')
+ );
+ }
+
+ public function testInfoUrlBeReadFromConfig()
+ {
+ $this->assertEquals(
+ 'https://top.example.com/',
+ $this->makeInstance()->loadProcess('simple_with-header')->getBpNode('top')->getInfoUrl()
+ );
+ }
+
+ public function testAConfiguredLoopCanBeParsed()
+ {
+ $this->assertInstanceOf(
+ $this->processClass,
+ $this->makeLoop()
+ );
+ }
+
+ public function testImportedNodesCanBeParsed()
+ {
+ $this->assertInstanceOf(
+ $this->processClass,
+ $this->makeInstance()->loadProcess('combined')
+ );
+ }
+
+ public function testConfigsWithNodesThatHaveSemicolonsInTheirNameCanBeParsed()
+ {
+ $bp = $this->makeInstance()->loadProcess('with-semicolons');
+
+ $this->assertInstanceOf($this->processClass, $bp);
+
+ $this->assertTrue($bp->hasNode('to\\;p'));
+ $this->assertSame(
+ 'https://top.example.com/',
+ $bp->getNode('to\\;p')->getInfoUrl()
+ );
+
+ $this->assertTrue($bp->hasNode('host\;1;Hoststatus'));
+ $this->assertSame('host;1', $bp->getNode('host\;1;Hoststatus')->getHostname());
+
+ $this->assertTrue($bp->hasNode('host\;1;pi;ng'));
+ $this->assertSame('host;1', $bp->getNode('host\;1;pi;ng')->getHostname());
+ $this->assertSame('pi;ng', $bp->getNode('host\;1;pi;ng')->getServiceDescription());
+
+ $this->assertTrue($bp->hasNode('singleHost'));
+ $this->assertTrue($bp->getNode('singleHost')->hasChild('to\\;p'));
+ $this->assertInstanceOf(BpNode::class, $bp->getNode('to\\;p'));
+
+ $this->assertInstanceOf(BpNode::class, $bp->getNode('no\\;alias'));
+ $this->assertSame('no;alias', $bp->getNode('no\\;alias')->getAlias());
+
+ $this->assertTrue($bp->hasNode('@also-with-semicolons:b\;ar'));
+ $this->assertTrue($bp->getNode('singleHost')->hasChild('@also-with-semicolons:b\;ar'));
+ $this->assertInstanceOf(ImportedNode::class, $bp->getNode('@also-with-semicolons:b\;ar'));
+ }
+}
diff --git a/test/php/library/Businessprocess/Web/Component/TabsTest.php b/test/php/library/Businessprocess/Web/Component/TabsTest.php
new file mode 100644
index 0000000..f1181d2
--- /dev/null
+++ b/test/php/library/Businessprocess/Web/Component/TabsTest.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Tests\Icinga\Module\Businessprocess\Web\Component;
+
+use Icinga\Module\Businessprocess\Web\Component\Tabs;
+use Icinga\Module\Businessprocess\Test\BaseTestCase;
+
+class TabsTest extends BaseTestCase
+{
+ public function testEmptyTabsCanBeInstantiated()
+ {
+ $this->assertInstanceOf(
+ 'Icinga\Module\Businessprocess\Web\Component\Tabs',
+ new Tabs()
+ );
+ }
+}
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..7d47ff8
--- /dev/null
+++ b/test/setup_vendor.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+set -ex
+
+MODULE_HOME=${MODULE_HOME:="$(dirname "$(readlink -f $(dirname "$0"))")"}
+PHP_VERSION="$(php -r 'echo phpversion();')"
+
+# see also .travis.yml
+ICINGAWEB_VERSION=${ICINGAWEB_VERSION:=2.5.1}
+ICINGAWEB_GITREF=${ICINGAWEB_GITREF:=}
+
+IPL_VERSION=${IPL_VERSION:=0.1.1}
+
+PHPCS_VERSION=${PHPCS_VERSION:=2.9.1}
+
+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
+ln -svf "${icingaweb_path}"/library/vendor/Zend
+
+# ipl
+ipl_path="ipl"
+if [ ! -d "$ipl_path" ]; then
+ git clone https://github.com/Icinga/icingaweb2-module-ipl.git "$ipl_path"
+fi
+(
+ set -e
+ cd "$ipl_path"
+ git fetch -p
+ git checkout -f "stable/$IPL_VERSION"
+)
+
+# 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