5 changed files with 303 additions and 33 deletions
@ -0,0 +1,215 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2026 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.iot_hub; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.node.ObjectNode; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
import org.mockito.InjectMocks; |
||||
|
import org.mockito.Mock; |
||||
|
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.dao.iot_hub.IotHubInstalledItemService; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
import static org.mockito.ArgumentMatchers.any; |
||||
|
import static org.mockito.ArgumentMatchers.anyString; |
||||
|
import static org.mockito.Mockito.mock; |
||||
|
import static org.mockito.Mockito.never; |
||||
|
import static org.mockito.Mockito.verify; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
|
||||
|
@ExtendWith(MockitoExtension.class) |
||||
|
class DefaultIotHubServiceTest { |
||||
|
|
||||
|
private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); |
||||
|
private final SecurityUser user = mock(SecurityUser.class); |
||||
|
|
||||
|
@Mock |
||||
|
private IotHubRestClient iotHubRestClient; |
||||
|
@Mock |
||||
|
private IotHubInstalledItemService iotHubInstalledItemService; |
||||
|
|
||||
|
@InjectMocks |
||||
|
private DefaultIotHubService service; |
||||
|
|
||||
|
private void mockTenant() { |
||||
|
when(user.getTenantId()).thenReturn(tenantId); |
||||
|
} |
||||
|
|
||||
|
private ObjectNode version(String id, String itemId, String type, String name, String ver) { |
||||
|
ObjectNode node = JacksonUtil.newObjectNode(); |
||||
|
node.put("id", id); |
||||
|
node.put("itemId", itemId); |
||||
|
node.put("type", type); |
||||
|
node.put("name", name); |
||||
|
node.put("version", ver); |
||||
|
return node; |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void resolveInstallPlan_rootOnly_noRelated_marksWillInstall() { |
||||
|
mockTenant(); |
||||
|
String versionId = UUID.randomUUID().toString(); |
||||
|
String itemId = UUID.randomUUID().toString(); |
||||
|
when(iotHubRestClient.getVersionInfo(versionId)).thenReturn(version(versionId, itemId, "DASHBOARD", "Root", "1.0")); |
||||
|
when(iotHubInstalledItemService.findInstalledItemIdsByTenantId(tenantId)).thenReturn(List.of()); |
||||
|
|
||||
|
InstallPlan plan = service.resolveInstallPlan(user, versionId); |
||||
|
|
||||
|
assertThat(plan.getEntries()).hasSize(1); |
||||
|
InstallPlanEntry root = plan.getEntries().get(0); |
||||
|
assertThat(root.isRoot()).isTrue(); |
||||
|
assertThat(root.getStatus()).isEqualTo(InstallPlanEntry.Status.WILL_INSTALL); |
||||
|
assertThat(root.getItemId()).isEqualTo(itemId); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void resolveInstallPlan_ordersDependenciesBeforeRoot() { |
||||
|
mockTenant(); |
||||
|
String versionId = UUID.randomUUID().toString(); |
||||
|
String rootItemId = UUID.randomUUID().toString(); |
||||
|
String relatedItemId = UUID.randomUUID().toString(); |
||||
|
ObjectNode root = version(versionId, rootItemId, "DASHBOARD", "Root", "1.0"); |
||||
|
root.putArray("relatedItems").add(relatedItemId); |
||||
|
when(iotHubRestClient.getVersionInfo(versionId)).thenReturn(root); |
||||
|
when(iotHubRestClient.getPublishedVersionByItemId(relatedItemId)) |
||||
|
.thenReturn(version(UUID.randomUUID().toString(), relatedItemId, "WIDGET", "Dep", "2.0")); |
||||
|
when(iotHubInstalledItemService.findInstalledItemIdsByTenantId(tenantId)).thenReturn(List.of()); |
||||
|
|
||||
|
InstallPlan plan = service.resolveInstallPlan(user, versionId); |
||||
|
|
||||
|
assertThat(plan.getEntries()).hasSize(2); |
||||
|
assertThat(plan.getEntries().get(0).getItemId()).isEqualTo(relatedItemId); |
||||
|
assertThat(plan.getEntries().get(0).isRoot()).isFalse(); |
||||
|
assertThat(plan.getEntries().get(1).getItemId()).isEqualTo(rootItemId); |
||||
|
assertThat(plan.getEntries().get(1).isRoot()).isTrue(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void resolveInstallPlan_missingRelated_recordedAsMissing() { |
||||
|
mockTenant(); |
||||
|
String versionId = UUID.randomUUID().toString(); |
||||
|
String rootItemId = UUID.randomUUID().toString(); |
||||
|
String relatedItemId = UUID.randomUUID().toString(); |
||||
|
ObjectNode root = version(versionId, rootItemId, "DASHBOARD", "Root", "1.0"); |
||||
|
root.putArray("relatedItems").add(relatedItemId); |
||||
|
when(iotHubRestClient.getVersionInfo(versionId)).thenReturn(root); |
||||
|
when(iotHubRestClient.getPublishedVersionByItemId(relatedItemId)).thenReturn(null); |
||||
|
when(iotHubInstalledItemService.findInstalledItemIdsByTenantId(tenantId)).thenReturn(List.of()); |
||||
|
|
||||
|
InstallPlan plan = service.resolveInstallPlan(user, versionId); |
||||
|
|
||||
|
InstallPlanEntry missing = plan.getEntries().get(0); |
||||
|
assertThat(missing.getStatus()).isEqualTo(InstallPlanEntry.Status.MISSING); |
||||
|
assertThat(missing.getItemId()).isEqualTo(relatedItemId); |
||||
|
assertThat(missing.getErrorMessage()).isNotBlank(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void resolveInstallPlan_relatedFetchThrows_recordedAsMissing() { |
||||
|
mockTenant(); |
||||
|
String versionId = UUID.randomUUID().toString(); |
||||
|
String rootItemId = UUID.randomUUID().toString(); |
||||
|
String relatedItemId = UUID.randomUUID().toString(); |
||||
|
ObjectNode root = version(versionId, rootItemId, "DASHBOARD", "Root", "1.0"); |
||||
|
root.putArray("relatedItems").add(relatedItemId); |
||||
|
when(iotHubRestClient.getVersionInfo(versionId)).thenReturn(root); |
||||
|
when(iotHubRestClient.getPublishedVersionByItemId(relatedItemId)).thenThrow(new RuntimeException("boom")); |
||||
|
when(iotHubInstalledItemService.findInstalledItemIdsByTenantId(tenantId)).thenReturn(List.of()); |
||||
|
|
||||
|
InstallPlan plan = service.resolveInstallPlan(user, versionId); |
||||
|
|
||||
|
assertThat(plan.getEntries().get(0).getStatus()).isEqualTo(InstallPlanEntry.Status.MISSING); |
||||
|
assertThat(plan.getEntries().get(0).getErrorMessage()).contains("boom"); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void resolveInstallPlan_alreadyInstalledRoot_marksAlreadyInstalled() { |
||||
|
mockTenant(); |
||||
|
String versionId = UUID.randomUUID().toString(); |
||||
|
UUID rootItemId = UUID.randomUUID(); |
||||
|
when(iotHubRestClient.getVersionInfo(versionId)) |
||||
|
.thenReturn(version(versionId, rootItemId.toString(), "DASHBOARD", "Root", "1.0")); |
||||
|
when(iotHubInstalledItemService.findInstalledItemIdsByTenantId(tenantId)).thenReturn(List.of(rootItemId)); |
||||
|
|
||||
|
InstallPlan plan = service.resolveInstallPlan(user, versionId); |
||||
|
|
||||
|
assertThat(plan.getEntries().get(0).getStatus()).isEqualTo(InstallPlanEntry.Status.ALREADY_INSTALLED); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void resolveInstallPlan_nullVersion_throws() { |
||||
|
mockTenant(); |
||||
|
String versionId = UUID.randomUUID().toString(); |
||||
|
when(iotHubRestClient.getVersionInfo(versionId)).thenReturn(null); |
||||
|
|
||||
|
assertThatThrownBy(() -> service.resolveInstallPlan(user, versionId)) |
||||
|
.isInstanceOf(IllegalArgumentException.class); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void installPlan_emptyPlan_returnsFailureWithoutInstalling() { |
||||
|
InstallPlanResult result = service.installPlan(user, new InstallPlan(null, List.of()), null, null); |
||||
|
|
||||
|
assertThat(result.isSuccess()).isFalse(); |
||||
|
assertThat(result.getErrorMessage()).contains("empty"); |
||||
|
verify(iotHubInstalledItemService, never()).findInstalledItemIdsByTenantId(any()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void installPlan_missingEntry_collectedAndNotInstalled() { |
||||
|
mockTenant(); |
||||
|
InstallPlanEntry missing = new InstallPlanEntry(); |
||||
|
missing.setItemId(UUID.randomUUID().toString()); |
||||
|
missing.setStatus(InstallPlanEntry.Status.MISSING); |
||||
|
when(iotHubInstalledItemService.findInstalledItemIdsByTenantId(tenantId)).thenReturn(List.of()); |
||||
|
|
||||
|
InstallPlanResult result = service.installPlan(user, new InstallPlan(null, List.of(missing)), null, null); |
||||
|
|
||||
|
assertThat(result.isSuccess()).isTrue(); |
||||
|
assertThat(result.getMissingItemIds()).containsExactly(missing.getItemId()); |
||||
|
assertThat(result.getRootDescriptor()).isNull(); |
||||
|
verify(iotHubRestClient, never()).getVersionInfo(anyString()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void installPlan_stalePlan_skipsItemInstalledSinceResolution() { |
||||
|
mockTenant(); |
||||
|
UUID itemId = UUID.randomUUID(); |
||||
|
InstallPlanEntry entry = new InstallPlanEntry(); |
||||
|
entry.setItemId(itemId.toString()); |
||||
|
entry.setVersionId(UUID.randomUUID().toString()); |
||||
|
entry.setStatus(InstallPlanEntry.Status.WILL_INSTALL); |
||||
|
entry.setRoot(true); |
||||
|
// The item is reported as already installed at install time, even though the plan says WILL_INSTALL.
|
||||
|
when(iotHubInstalledItemService.findInstalledItemIdsByTenantId(tenantId)).thenReturn(List.of(itemId)); |
||||
|
|
||||
|
InstallPlanResult result = service.installPlan(user, new InstallPlan(null, List.of(entry)), null, null); |
||||
|
|
||||
|
assertThat(result.isSuccess()).isTrue(); |
||||
|
assertThat(result.getEntries().get(0).getStatus()).isEqualTo(InstallPlanEntry.Status.ALREADY_INSTALLED); |
||||
|
// No marketplace fetch happened — the duplicate install was avoided.
|
||||
|
verify(iotHubRestClient, never()).getVersionInfo(anyString()); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue