From f22ec0fb16163824f2d6d82cb9d53a423ba88c6b Mon Sep 17 00:00:00 2001 From: Rom1-B <8530352+Rom1-B@users.noreply.github.com> Date: Mon, 25 May 2026 09:47:51 +0200 Subject: [PATCH 1/2] Fix: reduce MSSQL connections per collect; add unit tests --- CHANGELOG.md | 4 + inc/sccm.class.php | 110 ++-------- inc/sccmxml.class.php | 52 ++--- phpunit.xml | 18 ++ tests/PluginSccmSccmTest.php | 81 +++++++ tests/PluginSccmSccmxmlTest.php | 373 ++++++++++++++++++++++++++++++++ tests/bootstrap.php | 39 ++++ 7 files changed, 564 insertions(+), 113 deletions(-) create mode 100644 phpunit.xml create mode 100644 tests/PluginSccmSccmTest.php create mode 100644 tests/PluginSccmSccmxmlTest.php create mode 100644 tests/bootstrap.php 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..189dfd8 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..14caf0a 100644 --- a/inc/sccmxml.class.php +++ b/inc/sccmxml.class.php @@ -161,14 +161,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 +186,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 +236,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 +262,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 +282,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 +345,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 +380,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 +403,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..d60b653 --- /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"); +} From 7559a12d7be749043bde843c1e2ebced16883066 Mon Sep 17 00:00:00 2001 From: Rom1-B <8530352+Rom1-B@users.noreply.github.com> Date: Mon, 25 May 2026 09:54:41 +0200 Subject: [PATCH 2/2] lint --- inc/sccm.class.php | 2 +- inc/sccmxml.class.php | 1 - tests/bootstrap.php | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/inc/sccm.class.php b/inc/sccm.class.php index 189dfd8..d86e9b5 100644 --- a/inc/sccm.class.php +++ b/inc/sccm.class.php @@ -405,7 +405,7 @@ public static function executeCollect($task): int $sccm_db = new PluginSccmSccmdb(); if (!$sccm_db->connect($config_id)) { - throw new \RuntimeException( + throw new RuntimeException( sprintf('[Config %s] Cannot connect to SCCM database', $config_id), ); } diff --git a/inc/sccmxml.class.php b/inc/sccmxml.class.php index 14caf0a..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 { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d60b653..3744113 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -35,5 +35,5 @@ 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"); + throw new RuntimeException("Plugin $current_plugin_folder is not active in the test database"); }