diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad9140c..36090fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Handle multiple SCCM configuration
- Add `collection` scope
+- PHPUnit test suite for XML generation and query building logic
+### Fixed
+
+- Reduce MSSQL connections from N*8 per device to 2 per config by sharing a single connection across all per-device data fetching
## [2.5.1] - 2025-10-07
diff --git a/inc/sccm.class.php b/inc/sccm.class.php
index 7246c4c..d86e9b5 100644
--- a/inc/sccm.class.php
+++ b/inc/sccm.class.php
@@ -100,16 +100,8 @@ public function getDevices(int $config_id, $where = 0, $limit = 99999999): void
$sccm_db->disconnect();
}
- public function getDatas(int $config_id, $type, $deviceid, $limit = 99999999): array
+ public function getDatas(PluginSccmSccmdb $sccm_db, $type, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
if ($type == 'processors') {
$fields = ['Manufacturer00', 'Name00', 'NormSpeed00', 'AddressWidth00', 'CPUKey00', 'NumberOfCores00', 'NumberOfLogicalProcessors00'];
$table = 'Processor_DATA';
@@ -130,20 +122,11 @@ public function getDatas(int $config_id, $type, $deviceid, $limit = 99999999): a
$i++;
}
- $sccm_db->disconnect();
return $data;
}
- public function getNetwork(int $config_id, $deviceid, $limit = 99999999): array
+ public function getNetwork(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
$query = "SELECT NeDa.IPAddress00 as \"ND-IpAddress\",
NeDa.MACAddress00 as \"ND-MacAddress\",
NeDa.IPSubnet00 as \"ND-IpSubnet\",
@@ -166,20 +149,11 @@ public function getNetwork(int $config_id, $deviceid, $limit = 99999999): array
$i++;
}
- $sccm_db->disconnect();
return $data;
}
- public function getSoftware(int $config_id, $deviceid, $limit = 99999999): array
+ public function getSoftware(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
$query = "SELECT ArPd_64.DisplayName0 as \"ArPd-DisplayName\",
ArPd_64.InstallDate0 as \"ArPd-InstallDate\",
ArPd_64.Version0 as \"ArPd-Version\",
@@ -207,20 +181,11 @@ public function getSoftware(int $config_id, $deviceid, $limit = 99999999): array
$i++;
}
- $sccm_db->disconnect();
return $data;
}
- public function getMemories(int $config_id, $deviceid, $limit = 99999999): array
+ public function getMemories(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
$query = "SELECT
Capacity0 as \"Mem-Capacity\",
Caption0 as \"Mem-Caption\",
@@ -246,20 +211,11 @@ public function getMemories(int $config_id, $deviceid, $limit = 99999999): array
$i++;
}
- $sccm_db->disconnect();
return $data;
}
- public function getVideos(int $config_id, $deviceid, $limit = 99999999): array
+ public function getVideos(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
$query = "
SELECT
VideoProcessor0 as \"Vid-Chipset\",
@@ -281,20 +237,11 @@ public function getVideos(int $config_id, $deviceid, $limit = 99999999): array
$i++;
}
- $sccm_db->disconnect();
return $data;
}
- public function getSounds(int $config_id, $deviceid, $limit = 99999999): array
+ public function getSounds(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
$query = "
SELECT distinct
Description0 as \"Snd-Description\",
@@ -312,20 +259,11 @@ public function getSounds(int $config_id, $deviceid, $limit = 99999999): array
$i++;
}
- $sccm_db->disconnect();
return $data;
}
- public function getStorages(int $config_id, $deviceid, $limit = 99999999): array
+ public function getStorages(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
$query = "
SELECT
md.SystemName00,
@@ -352,20 +290,11 @@ public function getStorages(int $config_id, $deviceid, $limit = 99999999): array
$i++;
}
- $sccm_db->disconnect();
return $data;
}
- public function getMedias(int $config_id, $deviceid, $limit = 99999999): array
+ public function getMedias(PluginSccmSccmdb $sccm_db, $deviceid, $limit = 99999999): array
{
- $sccm_db = new PluginSccmSccmdb();
- $res = $sccm_db->connect($config_id);
- if (!$res) {
- throw new BadRequestHttpException(
- __s('Cannot connect to SCCM database', 'sccm'),
- );
- }
-
$query = "
SELECT distinct
Description0 as \"Med-Description\",
@@ -386,7 +315,6 @@ public function getMedias(int $config_id, $deviceid, $limit = 99999999): array
$i++;
}
- $sccm_db->disconnect();
return $data;
}
@@ -475,6 +403,13 @@ public static function executeCollect($task): int
true,
);
+ $sccm_db = new PluginSccmSccmdb();
+ if (!$sccm_db->connect($config_id)) {
+ throw new RuntimeException(
+ sprintf('[Config %s] Cannot connect to SCCM database', $config_id),
+ );
+ }
+
foreach ($PluginSccmSccm->devices as $device_values) {
$PluginSccmSccmxml = new PluginSccmSccmxml($device_values);
@@ -483,14 +418,14 @@ public static function executeCollect($task): int
$PluginSccmSccmxml->setHardware();
$PluginSccmSccmxml->setOS();
$PluginSccmSccmxml->setBios();
- $PluginSccmSccmxml->setProcessors($config_id);
- $PluginSccmSccmxml->setSoftwares($config_id);
- $PluginSccmSccmxml->setMemories($config_id);
- $PluginSccmSccmxml->setVideos($config_id);
- $PluginSccmSccmxml->setSounds($config_id);
+ $PluginSccmSccmxml->setProcessors($sccm_db);
+ $PluginSccmSccmxml->setSoftwares($sccm_db);
+ $PluginSccmSccmxml->setMemories($sccm_db);
+ $PluginSccmSccmxml->setVideos($sccm_db);
+ $PluginSccmSccmxml->setSounds($sccm_db);
$PluginSccmSccmxml->setUsers();
- $PluginSccmSccmxml->setNetworks($config_id);
- $PluginSccmSccmxml->setStorages($config_id);
+ $PluginSccmSccmxml->setNetworks($sccm_db);
+ $PluginSccmSccmxml->setStorages($sccm_db);
$SXML = $PluginSccmSccmxml->sxml;
$SXML->asXML($REP_XML . $PluginSccmSccmxml->device_id . ".ocs");
@@ -499,6 +434,7 @@ public static function executeCollect($task): int
$task->addVolume(1);
}
+ $sccm_db->disconnect();
Toolbox::logInFile('sccm', "[Config {$config_id}] Collect completed\n", true);
$retcode = 1;
} catch (Throwable $e) {
diff --git a/inc/sccmxml.class.php b/inc/sccmxml.class.php
index fd26130..9e82abc 100644
--- a/inc/sccmxml.class.php
+++ b/inc/sccmxml.class.php
@@ -31,7 +31,6 @@
use function Safe\preg_match;
use function Safe\preg_match_all;
-use function Safe\preg_replace;
class PluginSccmSccmxml
{
@@ -161,14 +160,14 @@ public function setBios(): void
$BIOS->addChild('SKUNUMBER', $this->data['PBD-Version']);
}
- public function setProcessors(int $config_id): void
+ public function setProcessors(PluginSccmSccmdb $sccm_db, ?PluginSccmSccm $sccm = null): void
{
- $sccm = new PluginSccmSccm();
+ $sccm ??= new PluginSccmSccm();
$cpukeys = [];
$CONTENT = $this->sxml->CONTENT[0];
$i = 0;
- foreach ($sccm->getDatas($config_id, 'processors', $this->device_id) as $value) {
+ foreach ($sccm->getDatas($sccm_db, 'processors', $this->device_id) as $value) {
if (!in_array($value['CPUKey00'], $cpukeys)) {
$CONTENT->addChild('CPUS');
$CPUS = $this->sxml->CONTENT[0]->CPUS[$i];
@@ -186,24 +185,24 @@ public function setProcessors(int $config_id): void
}
}
- public function setSoftwares(int $config_id): void
+ public function setSoftwares(PluginSccmSccmdb $sccm_db, ?PluginSccmSccm $sccm = null): void
{
- $sccm = new PluginSccmSccm();
+ $sccm ??= new PluginSccmSccm();
$antivirus = [];
$inject_antivirus = false;
$CONTENT = $this->sxml->CONTENT[0];
$i = 0;
- foreach ($sccm->getSoftware($config_id, $this->device_id) as $value) {
+ foreach ($sccm->getSoftware($sccm_db, $this->device_id) as $value) {
$CONTENT->addChild('SOFTWARES');
$SOFTWARES = $this->sxml->CONTENT[0]->SOFTWARES[$i];
- if (isset($value['ArPd-DisplayName']) && preg_match("#", $value['ArPd-DisplayName'])) {
- $value['ArPd-DisplayName'] = preg_replace("#", "&", $value['ArPd-DisplayName']);
+ if (isset($value['ArPd-DisplayName'])) {
+ $value['ArPd-DisplayName'] = str_replace('&', '&', $value['ArPd-DisplayName']);
}
- if (isset($value['ArPd-Publisher']) && preg_match("#", $value['ArPd-Publisher'])) {
- $value['ArPd-Publisher'] = preg_replace("#", "&", $value['ArPd-Publisher']);
+ if (isset($value['ArPd-Publisher'])) {
+ $value['ArPd-Publisher'] = str_replace('&', '&', $value['ArPd-Publisher']);
}
$SOFTWARES->addChild('NAME', $value['ArPd-DisplayName'] ?: NOT_AVAILABLE);
@@ -236,13 +235,13 @@ public function setSoftwares(int $config_id): void
}
}
- public function setMemories(int $config_id): void
+ public function setMemories(PluginSccmSccmdb $sccm_db, ?PluginSccmSccm $sccm = null): void
{
- $sccm = new PluginSccmSccm();
+ $sccm ??= new PluginSccmSccm();
$CONTENT = $this->sxml->CONTENT[0];
$i = 0;
- foreach ($sccm->getMemories($config_id, $this->device_id) as $value) {
+ foreach ($sccm->getMemories($sccm_db, $this->device_id) as $value) {
$CONTENT->addChild('MEMORIES');
$MEMORIES = $this->sxml->CONTENT[0]->MEMORIES[$i];
@@ -262,13 +261,13 @@ public function setMemories(int $config_id): void
}
}
- public function setVideos(int $config_id): void
+ public function setVideos(PluginSccmSccmdb $sccm_db, ?PluginSccmSccm $sccm = null): void
{
- $sccm = new PluginSccmSccm();
+ $sccm ??= new PluginSccmSccm();
$CONTENT = $this->sxml->CONTENT[0];
$i = 0;
- foreach ($sccm->getVideos($config_id, $this->device_id) as $value) {
+ foreach ($sccm->getVideos($sccm_db, $this->device_id) as $value) {
$CONTENT->addChild('VIDEOS');
$VIDEOS = $this->sxml->CONTENT[0]->VIDEOS[$i];
@@ -282,13 +281,13 @@ public function setVideos(int $config_id): void
}
}
- public function setSounds(int $config_id): void
+ public function setSounds(PluginSccmSccmdb $sccm_db, ?PluginSccmSccm $sccm = null): void
{
- $sccm = new PluginSccmSccm();
+ $sccm ??= new PluginSccmSccm();
$CONTENT = $this->sxml->CONTENT[0];
$i = 0;
- foreach ($sccm->getSounds($config_id, $this->device_id) as $value) {
+ foreach ($sccm->getSounds($sccm_db, $this->device_id) as $value) {
$CONTENT->addChild('SOUNDS');
$SOUNDS = $this->sxml->CONTENT[0]->SOUNDS[$i];
@@ -345,12 +344,12 @@ public function determineNetworkType(string $network_description): string
return "ethernet";
}
- public function setNetworks(int $config_id): void
+ public function setNetworks(PluginSccmSccmdb $sccm_db, ?PluginSccmSccm $sccm = null): void
{
- $sccm = new PluginSccmSccm();
+ $sccm ??= new PluginSccmSccm();
$CONTENT = $this->sxml->CONTENT[0];
- $networks = $sccm->getNetwork($config_id, $this->device_id);
+ $networks = $sccm->getNetwork($sccm_db, $this->device_id);
if ($networks !== []) {
$i = 0;
@@ -380,13 +379,13 @@ public function setNetworks(int $config_id): void
}
}
- public function setStorages(int $config_id): void
+ public function setStorages(PluginSccmSccmdb $sccm_db, ?PluginSccmSccm $sccm = null): void
{
- $sccm = new PluginSccmSccm();
+ $sccm ??= new PluginSccmSccm();
$CONTENT = $this->sxml->CONTENT[0];
$i = 0;
- foreach ($sccm->getStorages($config_id, $this->device_id) as $value) {
+ foreach ($sccm->getStorages($sccm_db, $this->device_id) as $value) {
$value['gld-TotalSize'] = intval($value['gld-TotalSize']) * 1024;
$value['gld-FreeSpace'] = intval($value['gld-FreeSpace']) * 1024;
@@ -403,7 +402,7 @@ public function setStorages(int $config_id): void
}
$i = 0;
- foreach ($sccm->getMedias($config_id, $this->device_id) as $value) {
+ foreach ($sccm->getMedias($sccm_db, $this->device_id) as $value) {
$CONTENT->addChild('STORAGES');
$STORAGES = $this->sxml->CONTENT[0]->STORAGES[$i];
$STORAGES->addChild('DESCRIPTION', $value['Med-Description']);
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..b827cdf
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,18 @@
+
+
+
+ src
+
+
+
+
+
+ tests
+
+
+
diff --git a/tests/PluginSccmSccmTest.php b/tests/PluginSccmSccmTest.php
new file mode 100644
index 0000000..2cb8ffa
--- /dev/null
+++ b/tests/PluginSccmSccmTest.php
@@ -0,0 +1,81 @@
+.
+ * -------------------------------------------------------------------------
+ * @author François Legastelois
+ * @copyright Copyright (C) 2014-2023 by SCCM plugin team.
+ * @license GPLv3 https://www.gnu.org/licenses/gpl-3.0.html
+ * @link https://github.com/pluginsGLPI/sccm
+ * -------------------------------------------------------------------------
+ */
+
+use Glpi\Tests\GLPITestCase;
+
+class PluginSccmSccmTest extends GLPITestCase
+{
+ public function testGetcomputerQueryBaseStructure(): void
+ {
+ $query = PluginSccmSccm::getcomputerQuery();
+
+ $this->assertStringContainsString('FROM Computer_System_DATA csd', $query);
+ $this->assertStringContainsString("WHERE csd.MachineID is not null and csd.MachineID != ''", $query);
+ }
+
+ public function testGetcomputerQueryWithoutCollectionHasNoFilter(): void
+ {
+ $query = PluginSccmSccm::getcomputerQuery();
+
+ $this->assertStringNotContainsString('v_FullCollectionMembership', $query);
+ }
+
+ public function testGetcomputerQueryEmptyCollectionHasNoFilter(): void
+ {
+ $query = PluginSccmSccm::getcomputerQuery('');
+
+ $this->assertStringNotContainsString('v_FullCollectionMembership', $query);
+ }
+
+ public function testGetcomputerQueryWithCollectionAddsSubquery(): void
+ {
+ $query = PluginSccmSccm::getcomputerQuery('All Workstations');
+
+ $this->assertStringContainsString('AND csd.MachineID IN', $query);
+ $this->assertStringContainsString('v_FullCollectionMembership', $query);
+ $this->assertStringContainsString("WHERE vc.Name = N'All Workstations'", $query);
+ }
+
+ public function testGetcomputerQueryEscapesSingleQuote(): void
+ {
+ $query = PluginSccmSccm::getcomputerQuery("O'Brien");
+
+ $this->assertStringContainsString("N'O''Brien'", $query);
+ $this->assertStringNotContainsString("N'O'Brien'", $query);
+ }
+
+ public function testGetcomputerQueryEscapesMultipleSingleQuotes(): void
+ {
+ $query = PluginSccmSccm::getcomputerQuery("It's O'Brien's");
+
+ $this->assertStringContainsString("N'It''s O''Brien''s'", $query);
+ }
+}
diff --git a/tests/PluginSccmSccmxmlTest.php b/tests/PluginSccmSccmxmlTest.php
new file mode 100644
index 0000000..fea7025
--- /dev/null
+++ b/tests/PluginSccmSccmxmlTest.php
@@ -0,0 +1,373 @@
+.
+ * -------------------------------------------------------------------------
+ * @author François Legastelois
+ * @copyright Copyright (C) 2014-2023 by SCCM plugin team.
+ * @license GPLv3 https://www.gnu.org/licenses/gpl-3.0.html
+ * @link https://github.com/pluginsGLPI/sccm
+ * -------------------------------------------------------------------------
+ */
+
+use Glpi\Tests\GLPITestCase;
+use PHPUnit\Framework\Attributes\DataProvider;
+
+class PluginSccmSccmxmlTest extends GLPITestCase
+{
+ private PluginSccmSccmxml $xml;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->xml = $this->getMockBuilder(PluginSccmSccmxml::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods([])
+ ->getMock();
+
+ $this->xml->sxml = new SimpleXMLElement(
+ "testtest-deviceINVENTORY",
+ );
+ }
+
+ public function testSetHardwareUppercasesName(): void
+ {
+ $this->xml->username = '';
+ $this->xml->data = ['MD-SystemName' => 'workstation01', 'SD-UUID' => 'guid:1234', 'CSD-Domain' => ''];
+
+ $this->xml->setHardware();
+
+ $this->assertSame('WORKSTATION01', (string) $this->xml->sxml->CONTENT[0]->HARDWARE->NAME);
+ }
+
+ public function testSetHardwareStripsGuidPrefix(): void
+ {
+ $this->xml->username = '';
+ $this->xml->data = ['MD-SystemName' => 'pc', 'SD-UUID' => 'guid:1234-5678-ABCD', 'CSD-Domain' => ''];
+
+ $this->xml->setHardware();
+
+ $this->assertSame('1234-5678-ABCD', (string) $this->xml->sxml->CONTENT[0]->HARDWARE->UUID);
+ }
+
+ #[DataProvider('accessLogUsernameProvider')]
+ public function testSetAccessLogResolvesUsername(array $data, string $expectedUsername): void
+ {
+ $this->xml->data = $data;
+
+ $this->xml->setAccessLog();
+
+ $this->assertSame($expectedUsername, $this->xml->username);
+ $this->assertSame($expectedUsername, (string) $this->xml->sxml->CONTENT[0]->ACCESSLOG->USERID);
+ }
+
+ public static function accessLogUsernameProvider(): iterable
+ {
+ yield 'VrS takes priority over SDI and CSD' => [
+ ['VrS-UserName' => 'alice', 'SDI-UserName' => 'bob', 'CSD-UserName' => 'DOMAIN charlie'],
+ 'alice',
+ ];
+ yield 'SDI used when VrS is empty' => [
+ ['VrS-UserName' => '', 'SDI-UserName' => 'bob', 'CSD-UserName' => ''],
+ 'bob',
+ ];
+ yield 'CSD with domain prefix extracts login after space' => [
+ ['VrS-UserName' => '', 'SDI-UserName' => '', 'CSD-UserName' => 'DOMAIN charlie'],
+ 'charlie',
+ ];
+ yield 'CSD without space used as-is' => [
+ ['VrS-UserName' => '', 'SDI-UserName' => '', 'CSD-UserName' => 'localuser'],
+ 'localuser',
+ ];
+ yield 'all sources empty gives empty username' => [
+ ['VrS-UserName' => '', 'SDI-UserName' => '', 'CSD-UserName' => ''],
+ '',
+ ];
+ }
+
+ #[DataProvider('biosDateProvider')]
+ public function testSetBiosNormalizesReleaseDate(mixed $releaseDate, string $expectedBdate): void
+ {
+ $this->xml->data = [
+ 'CSD-Model' => '',
+ 'SD-SystemRole' => '',
+ 'CSD-Manufacturer' => '',
+ 'PBD-SerialNumber' => '',
+ 'PBD-ReleaseDate' => $releaseDate,
+ 'PBD-Manufacturer' => '',
+ 'PBD-BiosVersion' => '',
+ 'PBD-Version' => '',
+ ];
+
+ $this->xml->setBios();
+
+ $this->assertSame($expectedBdate, (string) $this->xml->sxml->CONTENT[0]->BIOS->BDATE);
+ }
+
+ public static function biosDateProvider(): iterable
+ {
+ yield 'valid string date' => ['Jan 01 2020', '01/01/2020'];
+ yield 'end of year string date' => ['Dec 31 1999', '12/31/1999'];
+ yield 'DateTime object' => [new DateTime('2020-06-15'), '06/15/2020'];
+ yield 'invalid string preserved as-is' => ['invalid-date', 'invalid-date'];
+ }
+
+ #[DataProvider('networkTypeProvider')]
+ public function testDetermineNetworkType(string $description, string $expected): void
+ {
+ $this->assertSame($expected, $this->xml->determineNetworkType($description));
+ }
+
+ public static function networkTypeProvider(): iterable
+ {
+ yield 'wi-fi keyword' => ['Intel Wi-Fi 6 AX201', 'wifi'];
+ yield 'wireless keyword' => ['Broadcom Wireless Network Adapter', 'wifi'];
+ yield 'wifi keyword' => ['Realtek WiFi 5GHz', 'wifi'];
+ yield 'ethernet default' => ['Intel I219-V Gigabit Ethernet', 'ethernet'];
+ yield 'loopback' => ['Software Loopback Interface', 'loopback'];
+ yield 'bluetooth' => ['Bluetooth RFCOMM Device', 'bluetooth'];
+ yield 'bridge' => ['VMware Network Bridge Adapter', 'bridge'];
+ yield 'infiniband' => ['InfiniBand HCA Adapter', 'infiniband'];
+ yield 'aggregation' => ['Link Aggregation Port', 'aggregate'];
+ yield 'aggregate' => ['Bond Aggregate Interface', 'aggregate'];
+ yield 'alias' => ['Ethernet Alias 0', 'alias'];
+ yield 'dialup' => ['WAN Miniport Dialup', 'dialup'];
+ yield 'dial-up' => ['Generic Dial-Up Modem', 'dialup'];
+ yield 'fibre channel' => ['Emulex Fibre Channel HBA', 'fibrechannel'];
+ yield 'fiber channel' => ['QLogic Fiber Channel Adapter', 'fibrechannel'];
+ yield 'case insensitive' => ['INTEL WI-FI 6', 'wifi'];
+ yield 'empty string' => ['', 'ethernet'];
+ yield 'unknown type' => ['Generic Virtual Adapter', 'ethernet'];
+ }
+
+ public function testSetOSPopulatesNodes(): void
+ {
+ $this->xml->username = '';
+ $this->xml->data = [
+ 'MD-SystemName' => 'pc',
+ 'SD-UUID' => 'guid:1234',
+ 'CSD-Domain' => '',
+ 'OSD-Version' => '10.0.19041',
+ 'OSD-Caption' => 'Microsoft Windows 10 Pro',
+ 'OSD-CSDVersion' => 'Service Pack 1',
+ 'CSD-SystemType' => 'x64-based PC',
+ ];
+ $this->xml->setHardware(); // creates HARDWARE node that setOS() appends to
+
+ $this->xml->setOS();
+
+ $os = $this->xml->sxml->CONTENT[0]->OPERATINGSYSTEM;
+ $this->assertSame('10.0.19041', (string) $os->VERSION);
+ $this->assertSame('Microsoft Windows 10 Pro', (string) $os->NAME);
+ $this->assertSame('x64-based PC', (string) $os->ARCH);
+ $this->assertSame('Service Pack 1', (string) $os->SERVICE_PACK);
+ $this->assertSame('10.0.19041', (string) $this->xml->sxml->CONTENT[0]->HARDWARE->OSVERSION);
+ }
+
+ public function testSetAccountInfosSetsStaticValues(): void
+ {
+ $this->xml->setAccountInfos();
+
+ $info = $this->xml->sxml->CONTENT[0]->ACCOUNTINFO;
+ $this->assertSame('TAG', (string) $info->KEYNAME);
+ $this->assertSame('SCCM', (string) $info->KEYVALUE);
+ }
+
+ public function testSetAntivirusAddsNode(): void
+ {
+ $this->xml->setAntivirus('Kaspersky Endpoint Security 12');
+
+ $av = $this->xml->sxml->CONTENT[0]->ANTIVIRUS;
+ $this->assertSame('Kaspersky Endpoint Security 12', (string) $av->NAME);
+ }
+
+ public function testSetUsersAddsLoginNode(): void
+ {
+ $this->xml->username = 'jdoe';
+
+ $this->xml->setUsers();
+
+ $this->assertSame('jdoe', (string) $this->xml->sxml->CONTENT[0]->USERS->LOGIN);
+ }
+
+ private function makeSccmMock(string ...$methods): PluginSccmSccm
+ {
+ return $this->getMockBuilder(PluginSccmSccm::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods($methods)
+ ->getMock();
+ }
+
+ private function makeSccmDbMock(): PluginSccmSccmdb
+ {
+ return $this->getMockBuilder(PluginSccmSccmdb::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function testSetNetworksClassifiesIpv4(): void
+ {
+ $sccm = $this->makeSccmMock('getNetwork');
+ $sccm->method('getNetwork')->willReturn([[
+ 'ND-IpAddress' => '192.168.1.10',
+ 'ND-Name' => 'Intel I219-V Gigabit Ethernet',
+ 'ND-IpSubnet' => '255.255.255.0',
+ 'ND-DHCPServer' => '192.168.1.1',
+ 'ND-IpGateway' => '192.168.1.254',
+ 'ND-MacAddress' => 'AA:BB:CC:DD:EE:FF',
+ ]]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setNetworks($this->makeSccmDbMock(), $sccm);
+
+ $net = $this->xml->sxml->CONTENT[0]->NETWORKS[0];
+ $this->assertSame('192.168.1.10', (string) $net->IPADDRESS);
+ $this->assertSame(0, count($net->IPADDRESS6));
+ $this->assertSame('ethernet', (string) $net->TYPE);
+ }
+
+ public function testSetNetworksClassifiesIpv6(): void
+ {
+ $sccm = $this->makeSccmMock('getNetwork');
+ $sccm->method('getNetwork')->willReturn([[
+ 'ND-IpAddress' => '2001:db8::1',
+ 'ND-Name' => 'Intel I219-V Gigabit Ethernet',
+ 'ND-IpSubnet' => '',
+ 'ND-DHCPServer' => '',
+ 'ND-IpGateway' => '',
+ 'ND-MacAddress' => 'AA:BB:CC:DD:EE:FF',
+ ]]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setNetworks($this->makeSccmDbMock(), $sccm);
+
+ $net = $this->xml->sxml->CONTENT[0]->NETWORKS[0];
+ $this->assertSame(0, count($net->IPADDRESS));
+ $this->assertSame('2001:db8::1', (string) $net->IPADDRESS6);
+ }
+
+ public function testSetNetworksSplitsCommaDelimitedIps(): void
+ {
+ $sccm = $this->makeSccmMock('getNetwork');
+ $sccm->method('getNetwork')->willReturn([[
+ 'ND-IpAddress' => '10.0.0.1,2001:db8::1',
+ 'ND-Name' => 'Ethernet',
+ 'ND-IpSubnet' => '',
+ 'ND-DHCPServer' => '',
+ 'ND-IpGateway' => '',
+ 'ND-MacAddress' => 'AA:BB:CC:DD:EE:FF',
+ ]]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setNetworks($this->makeSccmDbMock(), $sccm);
+
+ $networks = $this->xml->sxml->CONTENT[0]->NETWORKS;
+ $this->assertCount(2, $networks);
+ $this->assertSame('10.0.0.1', (string) $networks[0]->IPADDRESS);
+ $this->assertSame('2001:db8::1', (string) $networks[1]->IPADDRESS6);
+ }
+
+ public function testSetSoftwaresFormatsInstallDate(): void
+ {
+ $sccm = $this->makeSccmMock('getSoftware');
+ $sccm->method('getSoftware')->willReturn([[
+ 'ArPd-DisplayName' => 'My App',
+ 'ArPd-Version' => '1.0',
+ 'ArPd-Publisher' => 'Acme',
+ 'ArPd-InstallDate' => '20200115',
+ ]]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setSoftwares($this->makeSccmDbMock(), $sccm);
+
+ $this->assertSame('15/01/2020', (string) $this->xml->sxml->CONTENT[0]->SOFTWARES[0]->INSTALLDATE);
+ }
+
+ public function testSetSoftwaresSkipsInvalidInstallDate(): void
+ {
+ $sccm = $this->makeSccmMock('getSoftware');
+ $sccm->method('getSoftware')->willReturn([[
+ 'ArPd-DisplayName' => 'My App',
+ 'ArPd-InstallDate' => 'not-a-date',
+ ]]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setSoftwares($this->makeSccmDbMock(), $sccm);
+
+ $this->assertSame(0, count($this->xml->sxml->CONTENT[0]->SOFTWARES[0]->INSTALLDATE));
+ }
+
+ public function testSetSoftwaresPreservesAmpersandInName(): void
+ {
+ $sccm = $this->makeSccmMock('getSoftware');
+ $sccm->method('getSoftware')->willReturn([[
+ 'ArPd-DisplayName' => 'AT&T Software',
+ 'ArPd-Publisher' => 'AT&T Corp',
+ ]]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setSoftwares($this->makeSccmDbMock(), $sccm);
+
+ $sw = $this->xml->sxml->CONTENT[0]->SOFTWARES[0];
+ $this->assertSame('AT&T Software', (string) $sw->NAME);
+ $this->assertSame('AT&T Corp', (string) $sw->PUBLISHER);
+ }
+
+ public function testSetSoftwaresDetectsKasperskyAntivirus(): void
+ {
+ $sccm = $this->makeSccmMock('getSoftware');
+ $sccm->method('getSoftware')->willReturn([[
+ 'ArPd-DisplayName' => 'Kaspersky Endpoint Security 12',
+ ]]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setSoftwares($this->makeSccmDbMock(), $sccm);
+
+ $this->assertSame(
+ 'Kaspersky Endpoint Security 12',
+ (string) $this->xml->sxml->CONTENT[0]->ANTIVIRUS->NAME,
+ );
+ }
+
+ public function testSetStoragesMultipliesSizeBy1024(): void
+ {
+ $sccm = $this->makeSccmMock('getStorages', 'getMedias');
+ $sccm->method('getStorages')->willReturn([[
+ 'gld-TotalSize' => '512',
+ 'gld-FreeSpace' => '128',
+ 'gld-Description' => 'Local Disk',
+ 'gld-Partition' => 'C:',
+ 'gld-FileSystem' => 'NTFS',
+ 'gld-MountingPoint' => 'C:',
+ 'gdi-Caption' => 'Disk C:',
+ ]]);
+ $sccm->method('getMedias')->willReturn([]);
+ $this->xml->device_id = 'test-device';
+
+ $this->xml->setStorages($this->makeSccmDbMock(), $sccm);
+
+ $drive = $this->xml->sxml->CONTENT[0]->DRIVES[0];
+ $this->assertSame('524288', (string) $drive->TOTAL);
+ $this->assertSame('131072', (string) $drive->FREE);
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..3744113
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,39 @@
+.
+ * -------------------------------------------------------------------------
+ * @author François Legastelois
+ * @copyright Copyright (C) 2014-2023 by SCCM plugin team.
+ * @license GPLv3 https://www.gnu.org/licenses/gpl-3.0.html
+ * @link https://github.com/pluginsGLPI/sccm
+ * -------------------------------------------------------------------------
+ */
+
+$current_plugin_folder = basename(realpath(__DIR__ . '/../'));
+
+require __DIR__ . '/../../../tests/bootstrap.php';
+require dirname(__DIR__) . '/vendor/autoload.php';
+
+if (!Plugin::isPluginActive($current_plugin_folder)) {
+ throw new RuntimeException("Plugin $current_plugin_folder is not active in the test database");
+}