diff --git a/src/DataSource/Lca/Boaviztapi/Client.php b/src/DataSource/Lca/Boaviztapi/Client.php index 5eeb0f12..0c7c657f 100644 --- a/src/DataSource/Lca/Boaviztapi/Client.php +++ b/src/DataSource/Lca/Boaviztapi/Client.php @@ -37,6 +37,7 @@ use GlpiPlugin\Carbon\Config as CarbonConfig; use GlpiPlugin\Carbon\DataSource\Lca\AbstractClient; use GlpiPlugin\Carbon\DataSource\RestApiClientInterface; +use GlpiPlugin\Carbon\DataTracking\AbstractTracked; use GlpiPlugin\Carbon\DataTracking\TrackedFloat; use GlpiPlugin\Carbon\Impact\Type; use GlpiPlugin\Carbon\Source; @@ -252,7 +253,7 @@ public static function getZones() * * @param array $response * @param string $scope (must be either embedded or use) - * @return array + * @return array */ public function parseResponse(array $response, string $scope): array { @@ -273,7 +274,7 @@ public function parseResponse(array $response, string $scope): array return $impacts; } - protected function parseCriteria(string $name, $impact): ?TrackedFloat + protected function parseCriteria(string $name, string|array $impact): ?TrackedFloat { if ($impact === 'not implemented') { return null; diff --git a/src/Impact/Embodied/AbstractEmbodiedImpact.php b/src/Impact/Embodied/AbstractEmbodiedImpact.php index 5a507bec..39bc0959 100644 --- a/src/Impact/Embodied/AbstractEmbodiedImpact.php +++ b/src/Impact/Embodied/AbstractEmbodiedImpact.php @@ -78,7 +78,12 @@ public function __construct(CommonDBTM $item) } } - abstract protected function getVersion(): string; + public function getEnginename(): string + { + return $this->engine; + } + + abstract public function getVersion(): string; #[Override] public function setLimit(int $limit) @@ -221,7 +226,7 @@ public static function getEvaluableQuery(string $itemtype, array $crit = [], boo /** * Do the environmental impact evaluation of an asset * - * @return ?array + * @return ?array */ abstract protected function doEvaluation(): ?array; diff --git a/src/Impact/Embodied/Boavizta/AbstractAsset.php b/src/Impact/Embodied/Boavizta/AbstractAsset.php index 8cf253c0..b80f4166 100644 --- a/src/Impact/Embodied/Boavizta/AbstractAsset.php +++ b/src/Impact/Embodied/Boavizta/AbstractAsset.php @@ -77,7 +77,7 @@ public function setClient(Client $client) } #[Override] - protected function getVersion(): string + public function getVersion(): string { if (self::$engine_version !== 'unknown') { return self::$engine_version; diff --git a/src/Impact/Embodied/Engine.php b/src/Impact/Embodied/Engine.php index 23e6c4c2..b362b268 100644 --- a/src/Impact/Embodied/Engine.php +++ b/src/Impact/Embodied/Engine.php @@ -57,7 +57,7 @@ public static function getAvailableBackends(): array } /** - * Get an instance of the engine to calculate imapcts for the given itemtype + * Get an instance of the engine to calculate impacts for the given itemtype * * Returns null if no engine found * @@ -68,10 +68,6 @@ public static function getEngineFromItemtype(CommonDBTM $item): ?EmbodiedImpactI { $itemtype = get_class($item); - if (self::hasModelData($item)) { - return self::getInternalEngineFromItemtype($item); - } - $embodied_impact_namespace = Config::getEmbodiedImpactEngine(); /** @var class-string $embodied_impact_class */ $embodied_impact_class = $embodied_impact_namespace . '\\' . $itemtype; @@ -80,14 +76,18 @@ public static function getEngineFromItemtype(CommonDBTM $item): ?EmbodiedImpactI return self::getInternalEngineFromItemtype($item); } - /** @var AbstractEmbodiedImpact $embodied_impact */ - $embodied_impact = new $embodied_impact_class($item); + /** @var AbstractEmbodiedImpact $external_embodied_impact_engine */ + $external_embodied_impact_engine = new $embodied_impact_class($item); try { - return self::configureEngine($embodied_impact); + $external_embodied_impact_engine = self::configureEngine($external_embodied_impact_engine); } catch (RuntimeException $e) { - // If the engine cannot be configured, it is not usable - return null; + $external_embodied_impact_engine = null; + } + if (self::hasModelData($item)) { + return self::getInternalEngineFromItemtype($item, $external_embodied_impact_engine); } + + return $external_embodied_impact_engine; } /** @@ -95,16 +95,17 @@ public static function getEngineFromItemtype(CommonDBTM $item): ?EmbodiedImpactI * This is a fallback engine * * @param CommonDBTM $item item to analyze + * @param ?EmbodiedImpactInterface $external_embodied_impact_engine * @return ?EmbodiedImpactInterface */ - public static function getInternalEngineFromItemtype(CommonDBTM $item): ?EmbodiedImpactInterface + public static function getInternalEngineFromItemtype(CommonDBTM $item, ?EmbodiedImpactInterface $external_embodied_impact_engine = null): ?EmbodiedImpactInterface { $itemtype = get_class($item); $embodied_impact_class = 'GlpiPlugin\\Carbon\\Impact\\Embodied\Internal' . '\\' . $itemtype; if (!class_exists($embodied_impact_class) || !is_subclass_of($embodied_impact_class, AbstractEmbodiedImpact::class)) { return null; } - $embodied_impact = new $embodied_impact_class($item); + $embodied_impact = new $embodied_impact_class($item, $external_embodied_impact_engine); return $embodied_impact; } diff --git a/src/Impact/Embodied/Internal/AbstractAsset.php b/src/Impact/Embodied/Internal/AbstractAsset.php index 06c43046..d9bc9c80 100644 --- a/src/Impact/Embodied/Internal/AbstractAsset.php +++ b/src/Impact/Embodied/Internal/AbstractAsset.php @@ -33,6 +33,7 @@ namespace GlpiPlugin\Carbon\Impact\Embodied\Internal; use CommonDBTM; +use DBmysql; use GlpiPlugin\Carbon\DataTracking\TrackedFloat; use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact; use GlpiPlugin\Carbon\Impact\Type; @@ -48,8 +49,16 @@ abstract class AbstractAsset extends AbstractEmbodiedImpact /** @var string $engine_version Version of the calculation engine */ protected static string $engine_version = '1'; + protected ?AbstractEmbodiedImpact $external_embodied_impact_engine = null; + + public function __construct(CommonDBTM $item, ?AbstractEmbodiedImpact $external_embodied_impact_engine) + { + parent::__construct($item); + $this->external_embodied_impact_engine = $external_embodied_impact_engine; + } + #[Override] - protected function getVersion(): string + public function getVersion(): string { return self::$engine_version; } @@ -57,33 +66,49 @@ protected function getVersion(): string #[Override] protected function doEvaluation(): array { - /** @var class-string */ + /** @var DBmysql $DB */ + global $DB; + + $impacts = []; + $glpi_model_itemtype = static::$itemtype . 'Model'; + $glpi_model_table = getTableForItemType($glpi_model_itemtype); $glpi_model_fk = getForeignKeyFieldForItemType($glpi_model_itemtype); - if ($glpi_model_itemtype::isNewID($this->item->fields[$glpi_model_fk])) { - return []; - } + $model_itemtype = 'GlpiPlugin\\Carbon\\' . $glpi_model_itemtype; + $model = getItemForItemtype($model_itemtype); + if ($model !== false) { + $model_table = getTableForItemtype($model_itemtype); + $model->getFromDBByRequest([ + 'INNER JOIN' => [ + $glpi_model_table => [ + 'ON' => [ + $glpi_model_table => 'id', + $model_table => $glpi_model_fk, + ], + ], + ], + 'WHERE' => [ + $glpi_model_fk => $this->item->fields[$glpi_model_fk], + ], + ]); - /** @var CommonDBTM $model */ - $model = getItemForItemtype($glpi_model_itemtype); - $model->getFromDBByCrit([ - $glpi_model_fk => $this->item->fields[$glpi_model_fk], - ]); - if ($model->isNewItem()) { - return []; - } + $types = Type::getImpactTypes(); - $impacts = []; - $types = Type::getImpactTypes(); - foreach ($types as $type) { - if (!isset($model->fields[$type]) || empty($model->fields[$type])) { - continue; + foreach ($types as $type) { + if (!isset($model->fields[$type]) || empty($model->fields[$type])) { + continue; + } + $impacts[Type::getImpactId($type)] = new TrackedFloat( + $model->fields[$type], + null, + $model->fields["{$type}_quality"] + ); } - $impacts[Type::getImpactId($type)] = new TrackedFloat( - $model->fields[$type], - null, - $model->fields["{$type}_quality"] - ); + } + if ($this->external_embodied_impact_engine !== null) { + $external_impacts = $this->external_embodied_impact_engine->doEvaluation(); + $this->engine .= ' + ' . $this->external_embodied_impact_engine->getEngineName() . ' ' . $this->external_embodied_impact_engine->getVersion(); + $impacts = array_replace($external_impacts, $impacts); } return $impacts; diff --git a/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php b/tests/src/Impact/Embodied/AbstractCommonEmbodiedImpactTest.php similarity index 92% rename from tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php rename to tests/src/Impact/Embodied/AbstractCommonEmbodiedImpactTest.php index 1a458c07..05726850 100644 --- a/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php +++ b/tests/src/Impact/Embodied/AbstractCommonEmbodiedImpactTest.php @@ -39,19 +39,22 @@ use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(AbstractEmbodiedImpact::class)] -class AbstractEmbodiedImpactTest extends DbTestCase +class AbstractCommonEmbodiedImpactTest extends DbTestCase { protected static string $itemtype = ''; protected static string $itemtype_type = ''; protected static string $itemtype_model = ''; - public function testGetItemsToEvaluate() + public static function setUpBeforeClass(): void { if (static::$itemtype === '' || static::$itemtype_type === '' || static::$itemtype_model === '') { // Ensure that the inherited test class is properly implemented for this test - $this->fail('Itemtype properties not set in ' . static::class); + self::fail('Itemtype properties not set in ' . static::class); } + } + public function test_GetItemsToEvaluate_evaluates_asset_when_no_embodied_impact_exists() + { // Test the asset is evaluable when no embodied impact is in the DB $glpi_asset_type = $this->createItem(static::$itemtype_type); $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ @@ -64,7 +67,10 @@ public function testGetItemsToEvaluate() $asset->getTableField('id') => $asset->getID(), ]); $this->assertEquals(1, $iterator->count()); + } + public function test_GetItemsToEvaluate_does_not_evaluates_asset_when_embodied_impact_exists() + { // Test the asset is no longer evaluable when there is embodied impact in the DB $glpi_asset_type = $this->createItem(static::$itemtype_type); $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ @@ -82,8 +88,11 @@ public function testGetItemsToEvaluate() $asset::getTableField('id') => $asset->getID(), ]); $this->assertEquals(0, $iterator->count()); + } - // Test the asset is evaluable when there is embodied impact in the DB but recamculate is set + public function test_GetItemsToEvaluate_evaluates_asset_when_embodied_impact_exists_and_marked_for_recalculation() + { + // Test the asset is evaluable when there is embodied impact in the DB but recalculate is set $glpi_asset_type = $this->createItem(static::$itemtype_type); $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ getForeignKeyFieldForItemType(static::$itemtype_type) => $glpi_asset_type->getID(), diff --git a/tests/src/Impact/Embodied/Boavizta/AbstractEmbodiedImpactTest.php b/tests/src/Impact/Embodied/Boavizta/AbstractEmbodiedImpactTest.php new file mode 100644 index 00000000..9e7d7dc4 --- /dev/null +++ b/tests/src/Impact/Embodied/Boavizta/AbstractEmbodiedImpactTest.php @@ -0,0 +1,45 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Tests\Impact\Embodied\Boavizta; + +use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact; +use GlpiPlugin\Carbon\Tests\Impact\Embodied\AbstractCommonEmbodiedImpactTest; +use PHPUnit\Framework\Attributes\CoversClass; + +#[CoversClass(AbstractEmbodiedImpact::class)] +class AbstractEmbodiedImpactTest extends AbstractCommonEmbodiedImpactTest +{ + protected static string $itemtype = ''; + protected static string $itemtype_type = ''; + protected static string $itemtype_model = ''; +} diff --git a/tests/src/Impact/Embodied/Internal/AbstractEmbodiedImpactTest.php b/tests/src/Impact/Embodied/Internal/AbstractEmbodiedImpactTest.php new file mode 100644 index 00000000..73ed4f75 --- /dev/null +++ b/tests/src/Impact/Embodied/Internal/AbstractEmbodiedImpactTest.php @@ -0,0 +1,83 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Tests\Impact\Embodied\Internal; + +use GlpiPlugin\Carbon\DataTracking\AbstractTracked; +use GlpiPlugin\Carbon\DataTracking\TrackedFloat; +use GlpiPlugin\Carbon\EmbodiedImpact; +use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact; +use GlpiPlugin\Carbon\Impact\Embodied\Boavizta\AbstractAsset; +use GlpiPlugin\Carbon\Impact\Embodied\Internal\Computer; +use GlpiPlugin\Carbon\Tests\Impact\Embodied\AbstractCommonEmbodiedImpactTest; +use PHPUnit\Framework\Attributes\CoversClass; + +#[CoversClass(AbstractEmbodiedImpact::class)] +class AbstractEmbodiedImpactTest extends AbstractCommonEmbodiedImpactTest +{ + protected static string $itemtype = ''; + protected static string $itemtype_type = ''; + protected static string $itemtype_model = ''; + + public function test_doEvaluation_uses_user_input_impacts_when_user_input_impacts_are_set() + { + $glpi_asset_type = $this->createItem(static::$itemtype_type); + $glpi_asset_model = $this->createItem(static::$itemtype_model); + $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ + getForeignKeyFieldForItemType(static::$itemtype_type) => $glpi_asset_type->getID(), + ]); + $asset_model = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_model, [ + getForeignKeyFieldForItemtype(static::$itemtype_model) => $glpi_asset_model->getID(), + 'gwp' => 1024, + 'gwp_quality' => AbstractTracked::DATA_QUALITY_MANUAL, + ]); + $asset = $this->createItem(static::$itemtype, [ + getForeignKeyFieldForItemType(static::$itemtype_type) => $glpi_asset_type->getID(), + getForeignKeyFieldForItemType(static::$itemtype_model) => $glpi_asset_model->getID(), + ]); + + $external_engine = $this->createStub(AbstractAsset::class); + $external_engine->method('doEvaluation')->willReturn([ + // 0 is the ID of GWP, see impact types + 0 => new TrackedFloat(2048, null, AbstractTracked::DATA_QUALITY_ESTIMATED), + ]); + $instance = new Computer($asset, $external_engine); + $instance->evaluateItem(); + + $embodied_impact = $this->getItem(EmbodiedImpact::class, [ + 'itemtype' => get_class($asset), + 'items_id' => $asset->getID(), + ]); + + $this->assertEquals(1024, $embodied_impact->fields['gwp']); + } +} diff --git a/tests/units/Impact/Embodied/Boavizta/ComputerTest.php b/tests/units/Impact/Embodied/Boavizta/ComputerTest.php index 6f071483..c91074e0 100644 --- a/tests/units/Impact/Embodied/Boavizta/ComputerTest.php +++ b/tests/units/Impact/Embodied/Boavizta/ComputerTest.php @@ -36,7 +36,7 @@ use ComputerModel as GlpiComputerModel; use ComputerType as GlpiComputerType; use GlpiPlugin\Carbon\Impact\Embodied\Boavizta\Computer as BoaviztaComputer; -use GlpiPlugin\Carbon\Tests\Impact\Embodied\AbstractEmbodiedImpactTest; +use GlpiPlugin\Carbon\Tests\Impact\Embodied\Boavizta\AbstractEmbodiedImpactTest; use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(BoaviztaComputer::class)] diff --git a/tests/units/Impact/Embodied/Boavizta/MonitorTest.php b/tests/units/Impact/Embodied/Boavizta/MonitorTest.php index 93aaa6f3..3ab862f8 100644 --- a/tests/units/Impact/Embodied/Boavizta/MonitorTest.php +++ b/tests/units/Impact/Embodied/Boavizta/MonitorTest.php @@ -33,7 +33,7 @@ namespace GlpiPlugin\Carbon\Impact\Embodied\Boavizta\Tests; use GlpiPlugin\Carbon\Impact\Embodied\Boavizta\Monitor as BoaviztaMonitor; -use GlpiPlugin\Carbon\Tests\Impact\Embodied\AbstractEmbodiedImpactTest; +use GlpiPlugin\Carbon\Tests\Impact\Embodied\Boavizta\AbstractEmbodiedImpactTest; use Monitor as GlpiMonitor; use MonitorModel as glpiMonitorModel; use MonitorType as GlpiMonitorType; diff --git a/tests/units/Impact/Embodied/Internal/ComputerTest.php b/tests/units/Impact/Embodied/Internal/ComputerTest.php new file mode 100644 index 00000000..78b12a42 --- /dev/null +++ b/tests/units/Impact/Embodied/Internal/ComputerTest.php @@ -0,0 +1,48 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Impact\Embodied\Internal\Tests; + +use Computer as GlpiComputer; +use ComputerModel as GlpiComputerModel; +use ComputerType as GlpiComputerType; +use GlpiPlugin\Carbon\Impact\Embodied\Internal\Computer as InternalComputer; +use GlpiPlugin\Carbon\Tests\Impact\Embodied\Internal\AbstractEmbodiedImpactTest; +use PHPUnit\Framework\Attributes\CoversClass; + +#[CoversClass(InternalComputer::class)] +class ComputerTest extends AbstractEmbodiedImpactTest +{ + protected static string $itemtype = GlpiComputer::class; + protected static string $itemtype_type = GlpiComputerType::class; + protected static string $itemtype_model = GlpiComputerModel::class; +}