mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Fix errors for some devices loading components (#11527)
* Test WIP * WIP * port getComponents to Eloquent * port more * simpler creation * change to explicit arrays * add missed file * restore commented code * fix inserting null value for component prefs * Fix some bugs in setCompenentPrefs Can't create tests without fixing bugs first :D * another test * another test * Modernize setComponentPrefs * Test for event log entries * Fix delete event * fix invalid values for component toggles * status log too * Use Setters to work around bad data, $casts doesn't do what we want.
This commit is contained in:
@@ -25,6 +25,11 @@
|
||||
|
||||
namespace LibreNMS;
|
||||
|
||||
use App\Models\ComponentPref;
|
||||
use App\Models\ComponentStatusLog;
|
||||
use Illuminate\Support\Arr;
|
||||
use Log;
|
||||
|
||||
class Component
|
||||
{
|
||||
/*
|
||||
@@ -32,45 +37,32 @@ class Component
|
||||
* so that they can be modified but they can not be set as user attributes. We
|
||||
* also set their default values.
|
||||
*/
|
||||
private $reserved = array(
|
||||
private $reserved = [
|
||||
'type' => '',
|
||||
'label' => '',
|
||||
'status' => 0,
|
||||
'ignore' => 0,
|
||||
'disabled' => 0,
|
||||
'error' => '',
|
||||
);
|
||||
];
|
||||
|
||||
public function getComponentCount($device_id = null)
|
||||
{
|
||||
if (is_null($device_id)) {
|
||||
// SELECT type, count(*) as count FROM component GROUP BY type
|
||||
$SQL = "SELECT `type` as `name`, count(*) as count FROM `component` GROUP BY `type`";
|
||||
$rows = dbFetchRows($SQL, array());
|
||||
} else {
|
||||
$SQL = "SELECT `type` as `name`, count(*) as count FROM `component` WHERE `device_id` = ? GROUP BY `type`";
|
||||
$rows = dbFetchRows($SQL, array($device_id));
|
||||
$counts = \App\Models\Component::query()->when($device_id, function ($query, $device_id) {
|
||||
$query->where('device_id', $device_id);
|
||||
})->selectRaw('type, count(*) as count')->groupBy('type')->pluck('count', 'type');
|
||||
|
||||
return $counts->isEmpty() ? false : $counts->all();
|
||||
}
|
||||
|
||||
if (isset($rows)) {
|
||||
// We found some, lets re-process to make more accessible
|
||||
$result = array();
|
||||
foreach ($rows as $value) {
|
||||
$result[$value['name']] = $value['count'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
// We didn't find any components
|
||||
return false;
|
||||
}
|
||||
public function getComponentType($TYPE = null)
|
||||
{
|
||||
if (is_null($TYPE)) {
|
||||
$SQL = "SELECT DISTINCT `type` as `name` FROM `component` ORDER BY `name`";
|
||||
$row = dbFetchRow($SQL, array());
|
||||
$row = dbFetchRow($SQL, []);
|
||||
} else {
|
||||
$SQL = "SELECT DISTINCT `type` as `name` FROM `component` WHERE `type` = ? ORDER BY `name`";
|
||||
$row = dbFetchRow($SQL, array($TYPE));
|
||||
$row = dbFetchRow($SQL, [$TYPE]);
|
||||
}
|
||||
|
||||
if (!isset($row)) {
|
||||
@@ -82,109 +74,56 @@ class Component
|
||||
}
|
||||
}
|
||||
|
||||
public function getComponents($device_id = null, $options = array())
|
||||
public function getComponents($device_id = null, $options = [])
|
||||
{
|
||||
// Define our results array, this will be set even if no rows are returned.
|
||||
$RESULT = array();
|
||||
$PARAM = array();
|
||||
|
||||
// Our base SQL Query, with no options.
|
||||
$SQL = "SELECT `C`.`id`,`C`.`device_id`,`C`.`type`,`C`.`label`,`C`.`status`,`C`.`disabled`,`C`.`ignore`,`C`.`error`,`CP`.`attribute`,`CP`.`value` FROM `component` as `C` LEFT JOIN `component_prefs` as `CP` on `C`.`id`=`CP`.`component` WHERE ";
|
||||
$query = \App\Models\Component::query()
|
||||
->with('prefs');
|
||||
|
||||
// Device_id is shorthand for filter C.device_id = $device_id.
|
||||
if (!is_null($device_id)) {
|
||||
$options['filter']['device_id'] = array('=', $device_id);
|
||||
$options['filter']['device_id'] = ['=', $device_id];
|
||||
}
|
||||
|
||||
// Type is shorthand for filter type = $type.
|
||||
if (isset($options['type'])) {
|
||||
$options['filter']['type'] = array('=', $options['type']);
|
||||
$options['filter']['type'] = ['=', $options['type']];
|
||||
}
|
||||
|
||||
$validFields = ['device_id', 'type', 'id', 'label', 'status', 'disabled', 'ignore', 'error'];
|
||||
|
||||
// filter field => array(operator,value)
|
||||
// Filters results based on the field, operator and value
|
||||
$COUNT = 0;
|
||||
if (isset($options['filter'])) {
|
||||
$COUNT++;
|
||||
$validFields = array('device_id','type','id','label','status','disabled','ignore','error');
|
||||
$SQL .= " ( ";
|
||||
foreach ($options['filter'] as $field => $array) {
|
||||
// Only add valid fields to the query
|
||||
if (in_array($field, $validFields)) {
|
||||
if ($array[0] == 'LIKE') {
|
||||
$SQL .= "`C`.`".$field."` LIKE ? AND ";
|
||||
$array[1] = "%".$array[1]."%";
|
||||
} else {
|
||||
// Equals operator is the default
|
||||
$SQL .= "`C`.`".$field."` = ? AND ";
|
||||
}
|
||||
array_push($PARAM, $array[1]);
|
||||
}
|
||||
}
|
||||
// Strip the last " AND " before closing the bracket.
|
||||
$SQL = substr($SQL, 0, -5)." )";
|
||||
}
|
||||
|
||||
if ($COUNT == 0) {
|
||||
// Strip the " WHERE " that we didn't use.
|
||||
$SQL = substr($SQL, 0, -7);
|
||||
foreach (array_intersect_key($options['filter'], array_flip($validFields)) as $field => $filter) {
|
||||
$op = $filter[0];
|
||||
$value = $op == 'LIKE' ? "%{$filter[1]}%" : $filter[1];
|
||||
$query->where($field, $op, $value);
|
||||
}
|
||||
|
||||
// sort column direction
|
||||
// Add SQL sorting to the results
|
||||
if (isset($options['sort'])) {
|
||||
$SQL .= " ORDER BY ".$options['sort'];
|
||||
}
|
||||
|
||||
// Get our component records using our built SQL.
|
||||
$COMPONENTS = dbFetchRows($SQL, $PARAM);
|
||||
|
||||
// if we have no components we need to return nothing
|
||||
if (count($COMPONENTS) == 0) {
|
||||
return $RESULT;
|
||||
}
|
||||
|
||||
// Add the AVP's to the array.
|
||||
foreach ($COMPONENTS as $COMPONENT) {
|
||||
if ($COMPONENT['attribute'] != "") {
|
||||
// if this component has attributes, set them in the array.
|
||||
$RESULT[$COMPONENT['device_id']][$COMPONENT['id']][$COMPONENT['attribute']] = $COMPONENT['value'];
|
||||
}
|
||||
}
|
||||
|
||||
// Populate our reserved fields into the Array, these cant be used as user attributes.
|
||||
foreach ($COMPONENTS as $COMPONENT) {
|
||||
$component_device_id = (int)$COMPONENT['device_id'];
|
||||
foreach ($this->reserved as $k => $v) {
|
||||
$RESULT[$component_device_id][$COMPONENT['id']][$k] = $COMPONENT[$k];
|
||||
}
|
||||
[$column, $direction] = explode(' ', $options['sort']);
|
||||
$query->orderBy($column, $direction);
|
||||
}
|
||||
|
||||
// limit array(start,count)
|
||||
if (isset($options['limit'])) {
|
||||
$TEMP = array();
|
||||
$COUNT = 0;
|
||||
// k = device_id, v = array of components for that device_id
|
||||
foreach ($RESULT as $k => $v) {
|
||||
// k1 = component id, v1 = component array
|
||||
foreach ($v as $k1 => $v1) {
|
||||
if (($COUNT >= $options['limit'][0]) && ($COUNT < $options['limit'][0]+$options['limit'][1])) {
|
||||
$TEMP[$k][$k1] = $v1;
|
||||
}
|
||||
// We are counting components.
|
||||
$COUNT++;
|
||||
}
|
||||
}
|
||||
$RESULT = $TEMP;
|
||||
$query->offset($options['limit'][0])->limit($options['limit'][1]);
|
||||
}
|
||||
|
||||
return $RESULT;
|
||||
// get and format results as expected by receivers
|
||||
return $query->get()->groupBy('device_id')->map(function ($group) {
|
||||
return $group->keyBy('id')->map(function ($component) {
|
||||
return $component->prefs->pluck('value', 'attribute')
|
||||
->merge($component->only(array_keys($this->reserved)));
|
||||
});
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
public function getComponentStatus($device = null)
|
||||
{
|
||||
$sql_query = "SELECT status, count(status) as count FROM component WHERE";
|
||||
$sql_param = array();
|
||||
$sql_param = [];
|
||||
$add = 0;
|
||||
|
||||
if (!is_null($device)) {
|
||||
@@ -205,7 +144,7 @@ class Component
|
||||
$result = dbFetchRows($sql_query, $sql_param);
|
||||
|
||||
// Set our defaults to 0
|
||||
$count = array(0 => 0, 1 => 0, 2 => 0);
|
||||
$count = [0 => 0, 1 => 0, 2 => 0];
|
||||
// Rebuild the array in a more convenient method
|
||||
foreach ($result as $v) {
|
||||
$count[$v['status']] = $v['count'];
|
||||
@@ -224,11 +163,11 @@ class Component
|
||||
}
|
||||
|
||||
// Create our return array.
|
||||
$return = array();
|
||||
$return = [];
|
||||
|
||||
// 1. find the previous value, this is the value when $start occurred.
|
||||
$sql_query = "SELECT status FROM `component_statuslog` WHERE `component_id` = ? AND `timestamp` < ? ORDER BY `id` desc LIMIT 1";
|
||||
$sql_param = array($component_id, $start);
|
||||
$sql_param = [$component_id, $start];
|
||||
$result = dbFetchRow($sql_query, $sql_param);
|
||||
if ($result == false) {
|
||||
$return['initial'] = false;
|
||||
@@ -238,136 +177,96 @@ class Component
|
||||
|
||||
// 2. Then we just need a list of all the entries for the time period.
|
||||
$sql_query = "SELECT status, `timestamp`, message FROM `component_statuslog` WHERE `component_id` = ? AND `timestamp` >= ? AND `timestamp` < ? ORDER BY `timestamp`";
|
||||
$sql_param = array($component_id, $start,$end);
|
||||
$sql_param = [$component_id, $start, $end];
|
||||
$return['data'] = dbFetchRows($sql_query, $sql_param);
|
||||
|
||||
d_echo("Status Log Data: " . print_r($return, true) . "\n");
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function createComponent($device_id, $TYPE)
|
||||
public function createComponent($device_id, $type)
|
||||
{
|
||||
// Prepare our default values to be inserted.
|
||||
$DATA = $this->reserved;
|
||||
|
||||
// Add the device_id and type
|
||||
$DATA['device_id'] = $device_id;
|
||||
$DATA['type'] = $TYPE;
|
||||
|
||||
// Insert a new component into the database.
|
||||
$id = dbInsert($DATA, 'component');
|
||||
$component = \App\Models\Component::create(['device_id' => $device_id, 'type' => $type]);
|
||||
|
||||
// Add a default status log entry - we always start ok.
|
||||
$this->createStatusLogEntry($id, 0, 'Component Created');
|
||||
$this->createStatusLogEntry($component->id, 0, 'Component Created');
|
||||
|
||||
// Create a default component array based on what was inserted.
|
||||
$ARRAY = array();
|
||||
$ARRAY[$id] = $DATA;
|
||||
unset($ARRAY[$id]['device_id']); // This doesn't belong here.
|
||||
return $ARRAY;
|
||||
return [$component->id => $component->only(array_keys($this->reserved))];
|
||||
}
|
||||
|
||||
public function createStatusLogEntry($component_id, $status, $message)
|
||||
{
|
||||
// Add an entry to the statuslog table for a particular component.
|
||||
$DATA = array(
|
||||
'component_id' => $component_id,
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
);
|
||||
try {
|
||||
return ComponentStatusLog::create(['component_id' => $component_id, 'status' => $status, 'message' => $message])->id;
|
||||
} catch (\Exception $e) {
|
||||
Log::debug("Failed to create component status log");
|
||||
}
|
||||
|
||||
return dbInsert($DATA, 'component_statuslog');
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function deleteComponent($id)
|
||||
{
|
||||
// Delete a component from the database.
|
||||
return dbDelete('component', "`id` = ?", array($id));
|
||||
return \App\Models\Component::destroy($id);
|
||||
}
|
||||
|
||||
public function setComponentPrefs($device_id, $ARRAY)
|
||||
public function setComponentPrefs($device_id, $updated)
|
||||
{
|
||||
// Compare the arrays. Update/Insert where necessary.
|
||||
$updated = Arr::wrap($updated);
|
||||
\App\Models\Component::whereIn('id', array_keys($updated))
|
||||
->with('prefs')
|
||||
->get()
|
||||
->each(function (\App\Models\Component $component) use ($updated) {
|
||||
unset($updated[$component->id]['type']); // can't change type
|
||||
|
||||
$OLD = $this->getComponents($device_id);
|
||||
// Loop over each component.
|
||||
foreach ((array)$ARRAY as $COMPONENT => $AVP) {
|
||||
// Make sure the component already exists.
|
||||
if (!isset($OLD[$device_id][$COMPONENT])) {
|
||||
// Error. Component doesn't exist in the database.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore type, we cant change that.
|
||||
unset($AVP['type'], $OLD[$device_id][$COMPONENT]['type']);
|
||||
// update component attributes
|
||||
$component->fill($updated[$component->id]);
|
||||
if ($component->isDirty()) {
|
||||
// Log the update to the Eventlog.
|
||||
$message = "Component $component->id has been modified: ";
|
||||
$message .= collect($component->getDirty())->map(function ($value, $key) {
|
||||
return "$key => $value";
|
||||
})->implode(',');
|
||||
|
||||
// If the Status has changed we need to add a log entry
|
||||
if ($AVP['status'] != $OLD[$device_id][$COMPONENT]['status']) {
|
||||
d_echo("Status Changed - Old: ".$OLD[$device_id][$COMPONENT]['status'].", New: ".$AVP['status']."\n");
|
||||
$this->createStatusLogEntry($COMPONENT['id'], $AVP['status'], $AVP['error']);
|
||||
if ($component->isDirty('status')) {
|
||||
Log::debug("Status Changed - Old: " . $component->getOriginal('status') . ", New: $component->status\n");
|
||||
$this->createStatusLogEntry($component->id, $component->status, $component->error);
|
||||
}
|
||||
$component->save();
|
||||
|
||||
Log::event($message, $component->device_id, 'component', 3, $component->id);
|
||||
}
|
||||
|
||||
// Process our reserved components first.
|
||||
$UPDATE = array();
|
||||
foreach ($this->reserved as $k => $v) {
|
||||
// does the reserved field exist, if not skip.
|
||||
if (isset($AVP[$k])) {
|
||||
// Has the value changed?
|
||||
if ($AVP[$k] != $OLD[$device_id][$COMPONENT][$k]) {
|
||||
// The value has been modified, add it to our update array.
|
||||
$UPDATE[$k] = $AVP[$k];
|
||||
}
|
||||
// update preferences
|
||||
$prefs = collect($updated[$component->id])->filter(function ($value, $attr) {
|
||||
return !array_key_exists($attr, $this->reserved);
|
||||
});
|
||||
|
||||
// Unset the reserved field. We don't want to insert it below.
|
||||
unset($AVP[$k], $OLD[$device_id][$COMPONENT][$k]);
|
||||
$invalid = $component->prefs->keyBy('id');
|
||||
|
||||
foreach ($prefs as $attribute => $value) {
|
||||
$existing = $component->prefs->firstWhere('attribute', $attribute);
|
||||
if ($existing) {
|
||||
$invalid->forget($existing->id);
|
||||
$existing->fill(['value' => $value]);
|
||||
if ($existing->isDirty()) {
|
||||
Log::event("Component: $component->type($component->id). Attribute: $attribute, was modified from: " . $existing->getOriginal('value') . ", to: $value", $device_id, 'component', 3, $component_id);
|
||||
$existing->save();
|
||||
}
|
||||
} else {
|
||||
$component->prefs()->save(new ComponentPref(['attribute' => $attribute, 'value' => $value]));
|
||||
Log::event("Component: $component->type($component->id). Attribute: $attribute, was added with value: $value", $component->device_id, 'component', 3, $component->id);
|
||||
}
|
||||
}
|
||||
|
||||
// Has anything changed, do we need to update?
|
||||
if (count($UPDATE) > 0) {
|
||||
// We have data to update
|
||||
dbUpdate($UPDATE, 'component', '`id` = ?', array($COMPONENT));
|
||||
|
||||
// Log the update to the Eventlog.
|
||||
$MSG = "Component ".$COMPONENT." has been modified: ";
|
||||
foreach ($UPDATE as $k => $v) {
|
||||
$MSG .= $k." => ".$v.",";
|
||||
}
|
||||
$MSG = substr($MSG, 0, -1);
|
||||
log_event($MSG, $device_id, 'component', 3, $COMPONENT);
|
||||
}
|
||||
|
||||
// Process our AVP Adds and Updates
|
||||
foreach ($AVP as $ATTR => $VALUE) {
|
||||
// We have our AVP, lets see if we need to do anything with it.
|
||||
|
||||
if (!isset($OLD[$device_id][$COMPONENT][$ATTR])) {
|
||||
// We have a newly added attribute, need to insert into the DB
|
||||
$DATA = array('component'=>$COMPONENT, 'attribute'=>$ATTR, 'value'=>$VALUE);
|
||||
dbInsert($DATA, 'component_prefs');
|
||||
|
||||
// Log the addition to the Eventlog.
|
||||
log_event("Component: " . $AVP[$COMPONENT]['type'] . "(" . $COMPONENT . "). Attribute: " . $ATTR . ", was added with value: " . $VALUE, $device_id, 'component', 3, $COMPONENT);
|
||||
} elseif ($OLD[$device_id][$COMPONENT][$ATTR] != $VALUE) {
|
||||
// Attribute exists but the value is different, need to update
|
||||
$DATA = array('value'=>$VALUE);
|
||||
dbUpdate($DATA, 'component_prefs', '`component` = ? AND `attribute` = ?', array($COMPONENT, $ATTR));
|
||||
|
||||
// Add the modification to the Eventlog.
|
||||
log_event("Component: " . $AVP[$COMPONENT]['type'] . "(" . $COMPONENT . "). Attribute: " . $ATTR . ", was modified from: " . $OLD[$device_id][$COMPONENT][$ATTR] . ", to: " . $VALUE, $device_id, 'component', 3, $COMPONENT);
|
||||
}
|
||||
} // End Foreach AVP
|
||||
|
||||
// Process our Deletes.
|
||||
$DELETE = array_diff_key($OLD[$device_id][$COMPONENT], $AVP);
|
||||
foreach ($DELETE as $KEY => $VALUE) {
|
||||
// As the Attribute has been removed from the array, we should remove it from the database.
|
||||
dbDelete('component_prefs', "`component` = ? AND `attribute` = ?", array($COMPONENT,$KEY));
|
||||
|
||||
// Log the addition to the Eventlog.
|
||||
log_event("Component: " . $AVP[$COMPONENT]['type'] . "(" . $COMPONENT . "). Attribute: " . $KEY . ", was deleted.", 4, $COMPONENT);
|
||||
}
|
||||
foreach ($invalid as $pref) {
|
||||
$pref->delete();
|
||||
Log::event("Component: $component->type($component->id). Attribute: $pref->attribute, was deleted.", $component->device_id, 'component', 4);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -29,4 +29,34 @@ class Component extends DeviceRelatedModel
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $table = 'component';
|
||||
protected $fillable = ['device_id', 'type', 'label', 'status', 'disabled', 'ignore', 'error'];
|
||||
|
||||
// ---- Accessors/Mutators ----
|
||||
|
||||
public function setStatusAttribute($status)
|
||||
{
|
||||
$this->attributes['status'] = (int)$status;
|
||||
}
|
||||
|
||||
public function setDisabledAttribute($disabled)
|
||||
{
|
||||
$this->attributes['disabled'] = (int)$disabled;
|
||||
}
|
||||
|
||||
public function setIgnoreAttribute($ignore)
|
||||
{
|
||||
$this->attributes['ignore'] = (int)$ignore;
|
||||
}
|
||||
|
||||
// ---- Define Relationships ----
|
||||
|
||||
public function logs()
|
||||
{
|
||||
return $this->hasMany(\App\Models\ComponentStatusLog::class, 'component_id', 'id');
|
||||
}
|
||||
|
||||
public function prefs()
|
||||
{
|
||||
return $this->hasMany(\App\Models\ComponentPref::class, 'component', 'id');
|
||||
}
|
||||
}
|
||||
|
16
app/Models/ComponentPref.php
Normal file
16
app/Models/ComponentPref.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ComponentPref extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['component', 'attribute', 'value'];
|
||||
|
||||
public function setValueAttribute($value)
|
||||
{
|
||||
$this->attributes['value'] = is_array($value) ? json_encode($value) : (string)$value;
|
||||
}
|
||||
}
|
19
app/Models/ComponentStatusLog.php
Normal file
19
app/Models/ComponentStatusLog.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ComponentStatusLog extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $table = 'component_statuslog';
|
||||
protected $fillable = ['component_id', 'status', 'message'];
|
||||
|
||||
// ---- Accessors/Mutators ----
|
||||
|
||||
public function setStatusAttribute($status)
|
||||
{
|
||||
$this->attributes['status'] = (int)$status;
|
||||
}
|
||||
}
|
@@ -31,4 +31,5 @@ class DeviceAttrib extends DeviceRelatedModel
|
||||
protected $primaryKey = 'attrib_id';
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['attrib_type', 'attrib_value'];
|
||||
// protected $casts = ['attrib_value' => 'array'];
|
||||
}
|
||||
|
@@ -131,3 +131,10 @@ $factory->define(\App\Models\Vminfo::class, function (Faker\Generator $faker) {
|
||||
'vmwVmState' => $faker->randomElement(['powered on', 'powered off', 'suspended']),
|
||||
];
|
||||
});
|
||||
|
||||
$factory->define(\App\Models\Component::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'device_id' => $faker->randomDigit,
|
||||
'type' => $faker->regexify('[A-Za-z0-9]{4,20}'),
|
||||
];
|
||||
});
|
||||
|
208
tests/Unit/ComponentTest.php
Normal file
208
tests/Unit/ComponentTest.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* ComponentTest.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2020 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Tests\Unit;
|
||||
|
||||
use App\Models\ComponentPref;
|
||||
use App\Models\ComponentStatusLog;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Str;
|
||||
use LibreNMS\Component;
|
||||
use LibreNMS\Tests\DBTestCase;
|
||||
|
||||
class ComponentTest extends DBTestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function testDeleteComponent()
|
||||
{
|
||||
$target = factory(\App\Models\Component::class)->create();
|
||||
|
||||
$this->assertTrue(\App\Models\Component::where('id', $target->id)->exists(), 'Failed to create component, this shouldn\'t happen');
|
||||
|
||||
$component = new Component();
|
||||
$component->deleteComponent($target->id);
|
||||
|
||||
$this->assertFalse(\App\Models\Component::where('id', $target->id)->exists(), 'deleteComponent failed to delete the component.');
|
||||
}
|
||||
|
||||
public function testGetComponentsEmpty()
|
||||
{
|
||||
$this->assertEquals([], (new Component())->getComponents(43));
|
||||
}
|
||||
|
||||
public function testGetComponentsOptionsType()
|
||||
{
|
||||
$target = factory(\App\Models\Component::class)->create();
|
||||
$component = new Component();
|
||||
|
||||
$actual = $component->getComponents($target->device_id, ['type' => $target->type]);
|
||||
|
||||
$this->assertCount(1, $actual);
|
||||
$this->assertArrayHasKey($target->device_id, $actual);
|
||||
$this->assertEquals($this->buildExpected($target), $actual);
|
||||
}
|
||||
|
||||
public function testGetComponentsOptionsFilterNotIgnore()
|
||||
{
|
||||
factory(\App\Models\Component::class)->create(['device_id' => 1, 'ignore' => 1]);
|
||||
$target = factory(\App\Models\Component::class)->times(2)->create(['device_id' => 1, 'ignore' => 0]);
|
||||
$component = new Component();
|
||||
|
||||
$actual = $component->getComponents(1, ['filter' => ['ignore' => ['=', 0]]]);
|
||||
|
||||
$this->assertEquals($this->buildExpected($target), $actual);
|
||||
}
|
||||
|
||||
public function testGetComponentsOptionsComplex()
|
||||
{
|
||||
factory(\App\Models\Component::class)->create(['label' => 'Search Phrase']);
|
||||
factory(\App\Models\Component::class)->times(2)->create(['label' => 'Something Else']);
|
||||
$target = factory(\App\Models\Component::class)->times(2)->create(['label' => 'Search Phrase']);
|
||||
factory(\App\Models\Component::class)->create(['label' => 'Search Phrase']);
|
||||
$component = new Component();
|
||||
|
||||
$options = [
|
||||
'filter' => ['label' => ['like', 'Search Phrase']],
|
||||
'sort' => 'id desc',
|
||||
'limit' => [1, 2],
|
||||
];
|
||||
$actual = $component->getComponents(null, $options);
|
||||
|
||||
$this->assertEquals($this->buildExpected($target->reverse()->values()), $actual);
|
||||
}
|
||||
|
||||
public function testGetFirstComponentID()
|
||||
{
|
||||
$input = [
|
||||
1 => [37 => [], 14 => []],
|
||||
2 => [19 => []],
|
||||
];
|
||||
$component = new Component();
|
||||
$this->assertEquals(19, $component->getFirstComponentID($input, 2));
|
||||
$this->assertEquals(37, $component->getFirstComponentID($input[1]));
|
||||
}
|
||||
|
||||
public function testGetComponentCount()
|
||||
{
|
||||
factory(\App\Models\Component::class)->times(2)->create(['device_id' => 1, 'type' => 'three']);
|
||||
factory(\App\Models\Component::class)->create(['device_id' => 2, 'type' => 'three']);
|
||||
factory(\App\Models\Component::class)->create(['device_id' => 2, 'type' => 'one']);
|
||||
|
||||
$component = new Component();
|
||||
$this->assertEquals(['three' => 3, 'one' => 1], $component->getComponentCount());
|
||||
$this->assertEquals(['three' => 2], $component->getComponentCount(1));
|
||||
$this->assertEquals(['three' => 1, 'one' => 1], $component->getComponentCount(2));
|
||||
}
|
||||
|
||||
public function testSetComponentPrefs()
|
||||
{
|
||||
// Nightmare function, no where near exhaustive
|
||||
$base = factory(\App\Models\Component::class)->create();
|
||||
$component = new Component();
|
||||
|
||||
\Log::shouldReceive('event')->withArgs(["Component: $base->type($base->id). Attribute: null_val, was added with value: ", $base->device_id, 'component', 3, $base->id]);
|
||||
$nullVal = $this->buildExpected($base)[$base->device_id];
|
||||
$nullVal[$base->id]['null_val'] = null;
|
||||
$component->setComponentPrefs($base->device_id, $nullVal);
|
||||
$this->assertEquals('', ComponentPref::where(['component' => $base->id, 'attribute' => 'null_val'])->first()->value);
|
||||
|
||||
\Log::shouldReceive('event')->withArgs(["Component $base->id has been modified: label => new label", $base->device_id, 'component', 3, $base->id]);
|
||||
\Log::shouldReceive('event')->withArgs(["Component: $base->type($base->id). Attribute: thirty, was added with value: 30", $base->device_id, 'component', 3, $base->id]);
|
||||
\Log::shouldReceive('event')->withArgs(["Component: $base->type($base->id). Attribute: json, was added with value: {\"json\":\"array\"}", $base->device_id, 'component', 3, $base->id]);
|
||||
\Log::shouldReceive('event')->withArgs(["Component: $base->type($base->id). Attribute: null_val, was deleted.", $base->device_id, 'component', 4]);
|
||||
$multiple = $this->buildExpected($base)[$base->device_id];
|
||||
$multiple[$base->id]['label'] = 'new label';
|
||||
$multiple[$base->id]['thirty'] = 30;
|
||||
$multiple[$base->id]['json'] = json_encode(['json' => 'array']);
|
||||
$component->setComponentPrefs($base->device_id, $multiple);
|
||||
|
||||
\Log::shouldReceive('event')->withArgs(["Component $base->id has been modified: label => ", $base->device_id, 'component', 3, $base->id]);
|
||||
\Log::shouldReceive('event')->withArgs(["Component: $base->type($base->id). Attribute: thirty, was deleted.", $base->device_id, 'component', 4]);
|
||||
\Log::shouldReceive('event')->withArgs(["Component: $base->type($base->id). Attribute: json, was deleted.", $base->device_id, 'component', 4]);
|
||||
$uc = \App\Models\Component::find($base->id);
|
||||
$this->assertEquals('new label', $uc->label);
|
||||
$this->assertEquals(30, $uc->prefs->where('attribute', 'thirty')->first()->value);
|
||||
$this->assertEquals($multiple[$base->id]['json'], $uc->prefs->where('attribute', 'json')->first()->value);
|
||||
|
||||
\Log::shouldReceive('event')->times(0);
|
||||
$remove = $this->buildExpected($base)[$base->device_id];
|
||||
$component->setComponentPrefs($base->device_id, $remove);
|
||||
$this->assertFalse(ComponentPref::where('component', $base->id)->exists());
|
||||
}
|
||||
|
||||
public function testCreateComponent()
|
||||
{
|
||||
$device_id = rand(1, 32);
|
||||
$type = Str::random(9);
|
||||
$component = (new Component())->createComponent($device_id, $type);
|
||||
|
||||
$this->assertCount(1, $component);
|
||||
$component_id = array_key_first($component);
|
||||
|
||||
$expected = ['type' => $type, 'label' => '', 'status' => 0, 'ignore' => 0, 'disabled' => 0, 'error' => ''];
|
||||
|
||||
$this->assertEquals([$component_id => $expected], $component);
|
||||
|
||||
$fromDb = \App\Models\Component::find($component_id);
|
||||
$this->assertEquals($device_id, $fromDb->device_id);
|
||||
$this->assertEquals($type, $fromDb->type);
|
||||
|
||||
$log = $fromDb->logs->first();
|
||||
$this->assertEquals($log->status, 0);
|
||||
$this->assertEquals($log->message, 'Component Created');
|
||||
}
|
||||
|
||||
public function testGetComponentStatusLog()
|
||||
{
|
||||
// invalid id fails
|
||||
$component = new Component();
|
||||
|
||||
$this->assertEquals(0, $component->createStatusLogEntry(434242, 0, 'failed'), 'incorrectly added log');
|
||||
|
||||
$message = Str::random(8);
|
||||
$model = factory(\App\Models\Component::class)->create();
|
||||
$log_id = $component->createStatusLogEntry($model->id, 1, $message);
|
||||
$this->assertNotEquals(0, $log_id, ' failed to create log');
|
||||
|
||||
$log = ComponentStatusLog::find($log_id);
|
||||
$this->assertEquals(1, $log->status);
|
||||
$this->assertEquals($message, $log->message);
|
||||
}
|
||||
|
||||
private function buildExpected($target)
|
||||
{
|
||||
$collection = $target instanceof \App\Models\Component ? collect([$target]) : $target;
|
||||
return $collection->groupBy('device_id')->map(function ($group) {
|
||||
return $group->keyBy('id')->map(function ($model) {
|
||||
$base = ['type' => null, 'label' => null, 'status' => 0, 'ignore' => 0, 'disabled' => 0, 'error' => null];
|
||||
$merge = $model->toArray();
|
||||
unset($merge['device_id'], $merge['id']);
|
||||
|
||||
return array_merge($base, $merge);
|
||||
});
|
||||
})->toArray();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user