Handle orphaned edge gateways for non-existent departments and improve error handling
This commit is contained in:
File diff suppressed because one or more lines are too long
+27
-1
@@ -41,7 +41,18 @@ class edge_gateway_department_workspace_service
|
||||
continue;
|
||||
}
|
||||
|
||||
$workspace = $this->buildDepartmentWorkspace($departmentId, false, $departmentRow);
|
||||
try {
|
||||
$workspace = $this->buildDepartmentWorkspace($departmentId, false, $departmentRow);
|
||||
} catch (Exception $exception) {
|
||||
if ($exception->getMessage() === 'Department not found') {
|
||||
$this->manager()->deleteOrphanedGatewaysForDepartment($departmentId);
|
||||
$this->clearDepartmentCache();
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$summaries[] = $workspace['summary'];
|
||||
}
|
||||
|
||||
@@ -897,4 +908,19 @@ class edge_gateway_department_workspace_service
|
||||
{
|
||||
return $this->manager ?? new edge_gateway_manager();
|
||||
}
|
||||
|
||||
private function clearDepartmentCache(): void
|
||||
{
|
||||
try {
|
||||
if (!defined('redis')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$redis = constant('redis');
|
||||
if (is_object($redis) && method_exists($redis, 'clear_departments')) {
|
||||
$redis->clear_departments();
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,16 @@ class edge_gateway_manager
|
||||
$gatewayIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$gatewayId = (int)$row['id'];
|
||||
$gateway = $this->requireGateway($gatewayId);
|
||||
try {
|
||||
$gateway = $this->requireGateway($gatewayId);
|
||||
} catch (Exception $exception) {
|
||||
if ($exception->getMessage() === 'Edge gateway not found') {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$gateways[] = $this->buildGatewayPayload($gateway, $includeDetail);
|
||||
$gatewayIds[] = $gatewayId;
|
||||
}
|
||||
@@ -709,6 +718,34 @@ class edge_gateway_manager
|
||||
];
|
||||
}
|
||||
|
||||
public function deleteOrphanedGatewaysForDepartment(int $departmentId): void
|
||||
{
|
||||
if ($departmentId <= 0 || $this->departmentExists($departmentId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = (new edge_gateways_o())->getFieldsWhere([
|
||||
'department_id' => $departmentId,
|
||||
'deleted_at' => null,
|
||||
], ['id']);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$gatewayId = (int)($row['id'] ?? 0);
|
||||
if ($gatewayId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$gateway = (new edge_gateways_o())->select($gatewayId);
|
||||
if ($gateway->exists()) {
|
||||
$this->softDeleteOrphanedGateway($gateway);
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
edge_gateway_view_cache::removeGateway($gatewayId, $departmentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -2339,9 +2376,47 @@ BASH;
|
||||
throw new Exception('Edge gateway not found');
|
||||
}
|
||||
|
||||
if (!$this->departmentExists((int)$gateway->department_id->value())) {
|
||||
$this->softDeleteOrphanedGateway($gateway);
|
||||
throw new Exception('Edge gateway not found');
|
||||
}
|
||||
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
private function departmentExists(int $departmentId): bool
|
||||
{
|
||||
if ($departmentId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return (new departments_o())->select($departmentId)->exists();
|
||||
} catch (\Throwable) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private function softDeleteOrphanedGateway(edge_gateways_o $gateway): void
|
||||
{
|
||||
$gatewayId = (int)$gateway->id;
|
||||
$departmentId = (int)$gateway->department_id->value();
|
||||
|
||||
try {
|
||||
$this->softDeleteGatewayRelations($gatewayId);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
try {
|
||||
if ($gateway->deleted_at->value() === null) {
|
||||
$gateway->deleted_at->set($this->now());
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
edge_gateway_view_cache::removeGateway($gatewayId, $departmentId > 0 ? $departmentId : null);
|
||||
}
|
||||
|
||||
private function softDeleteGatewayRelations(int $gatewayId): void
|
||||
{
|
||||
$tables = [
|
||||
|
||||
@@ -379,6 +379,68 @@ it('rejects operator edge routes when module permission or department access is
|
||||
->assertMissingPermissions(['department_access_' . (int)$department['id']]);
|
||||
});
|
||||
|
||||
it('ignores and soft-deletes edge gateways whose department no longer exists', function (): void {
|
||||
$session = api_fixtures()->createUserSession(['modules_shelly_config']);
|
||||
$missingDepartmentId = 2147483000;
|
||||
|
||||
$listOrphan = api_fixtures()->createClaimedEdgeGateway([
|
||||
'department_id' => $missingDepartmentId,
|
||||
'label' => 'Orphaned Fleet Gateway',
|
||||
]);
|
||||
api_fixtures()->clearEdgeGatewayViewCache();
|
||||
|
||||
$listResponse = api_client()->get('/edge-gateways', $session['headers']);
|
||||
|
||||
$listResponse
|
||||
->assertStatus(200)
|
||||
->assertEnvelope()
|
||||
->assertSuccess();
|
||||
|
||||
expect(collect_gateway_ids_from_api_response($listResponse->data()))
|
||||
->not->toContain((int)$listOrphan['id'])
|
||||
->and(api_fixtures()->fetchRowById('edge_gateways', (int)$listOrphan['id'])['deleted_at'] ?? null)
|
||||
->not->toBeNull();
|
||||
|
||||
$workspaceMissingDepartmentId = $missingDepartmentId - 1;
|
||||
$workspaceOrphan = api_fixtures()->createClaimedEdgeGateway([
|
||||
'department_id' => $workspaceMissingDepartmentId,
|
||||
'label' => 'Orphaned Workspace Gateway',
|
||||
]);
|
||||
api_fixtures()->clearEdgeGatewayViewCache();
|
||||
|
||||
$redis = api_test_runtime()->redis();
|
||||
expect($redis)->not->toBeNull();
|
||||
|
||||
$redis->set('departments', json_encode([[
|
||||
'id' => $workspaceMissingDepartmentId,
|
||||
'name' => 'Stale Department Cache',
|
||||
'description' => 'This department row is no longer in the database.',
|
||||
'order_priority' => 0,
|
||||
'visible' => 1,
|
||||
]], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
try {
|
||||
$workspaceResponse = api_client()->get(
|
||||
'/modules/edge-gateways/workspace/departments',
|
||||
$session['headers']
|
||||
);
|
||||
} finally {
|
||||
$redis->del(['departments']);
|
||||
}
|
||||
|
||||
$workspaceResponse
|
||||
->assertStatus(200)
|
||||
->assertEnvelope()
|
||||
->assertSuccess();
|
||||
|
||||
expect($workspaceResponse->data())
|
||||
->toBeArray()
|
||||
->and(collect_gateway_ids_from_api_response($workspaceResponse->data()))
|
||||
->not->toContain((int)$workspaceOrphan['id'])
|
||||
->and(api_fixtures()->fetchRowById('edge_gateways', (int)$workspaceOrphan['id'])['deleted_at'] ?? null)
|
||||
->not->toBeNull();
|
||||
});
|
||||
|
||||
function edge_operator_test_inventory(string $suffix): array
|
||||
{
|
||||
return [[
|
||||
|
||||
Reference in New Issue
Block a user