|
|
@ -31,9 +31,12 @@ import org.springframework.test.util.ReflectionTestUtils; |
|
|
import org.thingsboard.common.util.JacksonUtil; |
|
|
import org.thingsboard.common.util.JacksonUtil; |
|
|
import org.thingsboard.server.common.data.id.TenantId; |
|
|
import org.thingsboard.server.common.data.id.TenantId; |
|
|
import org.thingsboard.server.common.data.id.WidgetTypeId; |
|
|
import org.thingsboard.server.common.data.id.WidgetTypeId; |
|
|
|
|
|
import org.thingsboard.server.common.data.id.WidgetsBundleId; |
|
|
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
|
|
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
|
|
|
|
|
import org.thingsboard.server.common.data.widget.WidgetsBundle; |
|
|
import org.thingsboard.server.dao.resource.ImageService; |
|
|
import org.thingsboard.server.dao.resource.ImageService; |
|
|
import org.thingsboard.server.dao.widget.WidgetTypeService; |
|
|
import org.thingsboard.server.dao.widget.WidgetTypeService; |
|
|
|
|
|
import org.thingsboard.server.dao.widget.WidgetsBundleService; |
|
|
import org.thingsboard.server.service.install.DatabaseSchemaSettingsService; |
|
|
import org.thingsboard.server.service.install.DatabaseSchemaSettingsService; |
|
|
import org.thingsboard.server.service.install.InstallScripts; |
|
|
import org.thingsboard.server.service.install.InstallScripts; |
|
|
import org.thingsboard.server.service.system.SystemPatchApplier; |
|
|
import org.thingsboard.server.service.system.SystemPatchApplier; |
|
|
@ -41,6 +44,7 @@ import org.thingsboard.server.service.system.SystemPatchApplier; |
|
|
import java.nio.file.Files; |
|
|
import java.nio.file.Files; |
|
|
import java.nio.file.Path; |
|
|
import java.nio.file.Path; |
|
|
import java.util.Collections; |
|
|
import java.util.Collections; |
|
|
|
|
|
import java.util.List; |
|
|
import java.util.Set; |
|
|
import java.util.Set; |
|
|
import java.util.UUID; |
|
|
import java.util.UUID; |
|
|
import java.util.concurrent.CountDownLatch; |
|
|
import java.util.concurrent.CountDownLatch; |
|
|
@ -81,6 +85,9 @@ public class SystemPatchApplierTest { |
|
|
@Mock |
|
|
@Mock |
|
|
private WidgetTypeService widgetTypeService; |
|
|
private WidgetTypeService widgetTypeService; |
|
|
|
|
|
|
|
|
|
|
|
@Mock |
|
|
|
|
|
private WidgetsBundleService widgetsBundleService; |
|
|
|
|
|
|
|
|
@Mock |
|
|
@Mock |
|
|
private ImageService imageService; |
|
|
private ImageService imageService; |
|
|
|
|
|
|
|
|
@ -155,19 +162,72 @@ public class SystemPatchApplierTest { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Test |
|
|
@Test |
|
|
void whenWidgetNotFound_thenThrowException() throws Exception { |
|
|
void whenWidgetNotFound_thenCreateNewWidget() throws Exception { |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
|
|
|
|
|
|
WidgetTypeDetails testWidget = createTestWidgetType("test_widget", "Test Widget"); |
|
|
WidgetTypeDetails fileWidget = createTestWidgetType("new_widget", "New Widget"); |
|
|
String json = JacksonUtil.toString(testWidget); |
|
|
String json = JacksonUtil.toString(fileWidget); |
|
|
assertNotNull(json); |
|
|
assertNotNull(json); |
|
|
Files.writeString(widgetTypesDir.resolve("test_widget.json"), json); |
|
|
Files.writeString(widgetTypesDir.resolve("new_widget.json"), json); |
|
|
|
|
|
|
|
|
|
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "new_widget")).thenReturn(null); |
|
|
|
|
|
|
|
|
|
|
|
SystemPatchApplier.WidgetTypeStats stats = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
|
|
|
|
|
|
|
|
|
assertNotNull(stats); |
|
|
|
|
|
assertEquals(1, stats.created()); |
|
|
|
|
|
assertEquals(0, stats.updated()); |
|
|
|
|
|
verify(widgetTypeService).saveWidgetType(argThat(w -> "new_widget".equals(w.getFqn()))); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenFqnIsBlank_thenThrowException() throws Exception { |
|
|
|
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
|
|
|
Files.createDirectories(widgetTypesDir); |
|
|
|
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
|
|
|
|
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "test_widget")).thenReturn(null); |
|
|
WidgetTypeDetails brokenWidget = createTestWidgetType("", "Broken Widget"); |
|
|
|
|
|
String json = JacksonUtil.toString(brokenWidget); |
|
|
|
|
|
assertNotNull(json); |
|
|
|
|
|
Files.writeString(widgetTypesDir.resolve("broken.json"), json); |
|
|
|
|
|
|
|
|
assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes")); |
|
|
assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes")); |
|
|
|
|
|
verify(widgetTypeService, never()).saveWidgetType(any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenMixOfCreatedAndUpdated_thenStatsAreCorrect() throws Exception { |
|
|
|
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
|
|
|
Files.createDirectories(widgetTypesDir); |
|
|
|
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
|
|
|
|
|
|
|
|
|
WidgetTypeDetails newFileWidget = createTestWidgetType("widget_new", "Widget New"); |
|
|
|
|
|
Files.writeString(widgetTypesDir.resolve("widget_new.json"), JacksonUtil.toString(newFileWidget)); |
|
|
|
|
|
|
|
|
|
|
|
WidgetTypeDetails changedFileWidget = createTestWidgetType("widget_changed", "Widget Changed New Name"); |
|
|
|
|
|
Files.writeString(widgetTypesDir.resolve("widget_changed.json"), JacksonUtil.toString(changedFileWidget)); |
|
|
|
|
|
|
|
|
|
|
|
WidgetTypeDetails sameFileWidget = createTestWidgetType("widget_same", "Widget Same"); |
|
|
|
|
|
Files.writeString(widgetTypesDir.resolve("widget_same.json"), JacksonUtil.toString(sameFileWidget)); |
|
|
|
|
|
|
|
|
|
|
|
WidgetTypeDetails existingChanged = createTestWidgetType("widget_changed", "Widget Changed Old Name"); |
|
|
|
|
|
existingChanged.setId(new WidgetTypeId(UUID.randomUUID())); |
|
|
|
|
|
|
|
|
|
|
|
WidgetTypeDetails existingSame = createTestWidgetType("widget_same", "Widget Same"); |
|
|
|
|
|
existingSame.setId(new WidgetTypeId(UUID.randomUUID())); |
|
|
|
|
|
|
|
|
|
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "widget_new")).thenReturn(null); |
|
|
|
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "widget_changed")).thenReturn(existingChanged); |
|
|
|
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "widget_same")).thenReturn(existingSame); |
|
|
|
|
|
|
|
|
|
|
|
SystemPatchApplier.WidgetTypeStats stats = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
|
|
|
|
|
|
|
|
|
assertNotNull(stats); |
|
|
|
|
|
assertEquals(1, stats.created()); |
|
|
|
|
|
assertEquals(1, stats.updated()); |
|
|
|
|
|
verify(widgetTypeService, times(2)).saveWidgetType(any()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Test |
|
|
@Test |
|
|
@ -189,9 +249,11 @@ public class SystemPatchApplierTest { |
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "test_widget")) |
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "test_widget")) |
|
|
.thenReturn(existingWidget); |
|
|
.thenReturn(existingWidget); |
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
SystemPatchApplier.WidgetTypeStats stats = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
|
|
|
|
|
|
assertEquals(1, updated); |
|
|
assertNotNull(stats); |
|
|
|
|
|
assertEquals(0, stats.created()); |
|
|
|
|
|
assertEquals(1, stats.updated()); |
|
|
verify(widgetTypeService).saveWidgetType(argThat(w -> |
|
|
verify(widgetTypeService).saveWidgetType(argThat(w -> |
|
|
w.getDescriptor().get("version").asInt() == 2 |
|
|
w.getDescriptor().get("version").asInt() == 2 |
|
|
)); |
|
|
)); |
|
|
@ -214,9 +276,11 @@ public class SystemPatchApplierTest { |
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "test_widget")) |
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "test_widget")) |
|
|
.thenReturn(existingWidget); |
|
|
.thenReturn(existingWidget); |
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
SystemPatchApplier.WidgetTypeStats stats = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
|
|
|
|
|
|
assertEquals(1, updated); |
|
|
assertNotNull(stats); |
|
|
|
|
|
assertEquals(0, stats.created()); |
|
|
|
|
|
assertEquals(1, stats.updated()); |
|
|
verify(widgetTypeService).saveWidgetType(argThat(w -> "New Name".equals(w.getName()))); |
|
|
verify(widgetTypeService).saveWidgetType(argThat(w -> "New Name".equals(w.getName()))); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -237,9 +301,11 @@ public class SystemPatchApplierTest { |
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "test_widget")) |
|
|
when(widgetTypeService.findWidgetTypeDetailsByTenantIdAndFqn(TenantId.SYS_TENANT_ID, "test_widget")) |
|
|
.thenReturn(existingWidget); |
|
|
.thenReturn(existingWidget); |
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
SystemPatchApplier.WidgetTypeStats stats = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
|
|
|
|
|
|
assertEquals(0, updated); |
|
|
assertNotNull(stats); |
|
|
|
|
|
assertEquals(0, stats.created()); |
|
|
|
|
|
assertEquals(0, stats.updated()); |
|
|
verify(widgetTypeService, never()).saveWidgetType(any()); |
|
|
verify(widgetTypeService, never()).saveWidgetType(any()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -339,8 +405,8 @@ public class SystemPatchApplierTest { |
|
|
// Simulate work while holding lock
|
|
|
// Simulate work while holding lock
|
|
|
Thread.sleep(100); |
|
|
Thread.sleep(100); |
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
SystemPatchApplier.WidgetTypeStats stats = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
firstThreadSavedWidget.set(updated != null && updated > 0); |
|
|
firstThreadSavedWidget.set(stats != null && stats.updated() > 0); |
|
|
|
|
|
|
|
|
ReflectionTestUtils.invokeMethod(reconciler, "releaseAdvisoryLock"); |
|
|
ReflectionTestUtils.invokeMethod(reconciler, "releaseAdvisoryLock"); |
|
|
} |
|
|
} |
|
|
@ -360,8 +426,8 @@ public class SystemPatchApplierTest { |
|
|
secondThreadAcquiredLock.set(Boolean.TRUE.equals(acquired)); |
|
|
secondThreadAcquiredLock.set(Boolean.TRUE.equals(acquired)); |
|
|
|
|
|
|
|
|
if (secondThreadAcquiredLock.get()) { |
|
|
if (secondThreadAcquiredLock.get()) { |
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
SystemPatchApplier.WidgetTypeStats stats = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetTypes"); |
|
|
secondThreadSavedWidget.set(updated != null && updated > 0); |
|
|
secondThreadSavedWidget.set(stats != null && stats.updated() > 0); |
|
|
|
|
|
|
|
|
ReflectionTestUtils.invokeMethod(reconciler, "releaseAdvisoryLock"); |
|
|
ReflectionTestUtils.invokeMethod(reconciler, "releaseAdvisoryLock"); |
|
|
} |
|
|
} |
|
|
@ -560,6 +626,7 @@ public class SystemPatchApplierTest { |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(tempDir.resolve("widget_bundles_missing")); |
|
|
|
|
|
|
|
|
ReflectionTestUtils.invokeMethod(reconciler, "applyPatchIfNeeded"); |
|
|
ReflectionTestUtils.invokeMethod(reconciler, "applyPatchIfNeeded"); |
|
|
|
|
|
|
|
|
@ -607,6 +674,7 @@ public class SystemPatchApplierTest { |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(tempDir.resolve("widget_bundles_missing")); |
|
|
|
|
|
|
|
|
ReflectionTestUtils.invokeMethod(reconciler, "applyPatchIfNeeded"); |
|
|
ReflectionTestUtils.invokeMethod(reconciler, "applyPatchIfNeeded"); |
|
|
|
|
|
|
|
|
@ -834,6 +902,7 @@ public class SystemPatchApplierTest { |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Path widgetTypesDir = tempDir.resolve("widget_types"); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
Files.createDirectories(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
when(installScripts.getWidgetTypesDir()).thenReturn(widgetTypesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(tempDir.resolve("widget_bundles_missing")); |
|
|
|
|
|
|
|
|
when(imageService.getAllImageKeysByTenantId(TenantId.SYS_TENANT_ID)).thenReturn(Collections.emptySet()); |
|
|
when(imageService.getAllImageKeysByTenantId(TenantId.SYS_TENANT_ID)).thenReturn(Collections.emptySet()); |
|
|
|
|
|
|
|
|
@ -854,4 +923,201 @@ public class SystemPatchApplierTest { |
|
|
verify(imageService, never()).createOrUpdateSystemImage(anyString(), any(byte[].class)); |
|
|
verify(imageService, never()).createOrUpdateSystemImage(anyString(), any(byte[].class)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// --- updateWidgetBundles tests ---
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenWidgetBundlesDirDoesNotExist_thenReturnsZero() { |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(tempDir.resolve("missing_bundles")); |
|
|
|
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles"); |
|
|
|
|
|
|
|
|
|
|
|
assertEquals(0, updated); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
verify(widgetTypeService, never()).updateWidgetsBundleWidgetFqns(any(), any(), any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenBundleNotInDb_thenSkipWithoutCreation() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("charts.json"), |
|
|
|
|
|
"{\"widgetsBundle\":{\"alias\":\"charts\",\"title\":\"Charts\",\"order\":10}," + |
|
|
|
|
|
"\"widgetTypeFqns\":[\"line_chart\"]}"); |
|
|
|
|
|
|
|
|
|
|
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "charts")).thenReturn(null); |
|
|
|
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles"); |
|
|
|
|
|
|
|
|
|
|
|
assertEquals(0, updated); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
verify(widgetTypeService, never()).updateWidgetsBundleWidgetFqns(any(), any(), any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenBundleExistsAndHasNewFqn_thenMergeFqns() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("charts.json"), |
|
|
|
|
|
"{\"widgetsBundle\":{\"alias\":\"charts\",\"title\":\"Charts\",\"description\":\"d\",\"order\":10}," + |
|
|
|
|
|
"\"widgetTypeFqns\":[\"line_chart\",\"bar_chart\",\"new_chart\"]}"); |
|
|
|
|
|
|
|
|
|
|
|
WidgetsBundle existingBundle = createTestBundle("charts", "Charts"); |
|
|
|
|
|
existingBundle.setDescription("d"); |
|
|
|
|
|
existingBundle.setOrder(10); |
|
|
|
|
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "charts")).thenReturn(existingBundle); |
|
|
|
|
|
when(widgetTypeService.findWidgetFqnsByWidgetsBundleId(TenantId.SYS_TENANT_ID, existingBundle.getId())) |
|
|
|
|
|
.thenReturn(List.of("line_chart", "bar_chart")); |
|
|
|
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles"); |
|
|
|
|
|
|
|
|
|
|
|
assertEquals(1, updated); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
verify(widgetTypeService).updateWidgetsBundleWidgetFqns( |
|
|
|
|
|
eq(TenantId.SYS_TENANT_ID), |
|
|
|
|
|
eq(existingBundle.getId()), |
|
|
|
|
|
argThat(fqns -> fqns.size() == 3 |
|
|
|
|
|
&& fqns.get(0).equals("line_chart") |
|
|
|
|
|
&& fqns.get(1).equals("bar_chart") |
|
|
|
|
|
&& fqns.get(2).equals("new_chart")) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenBundleExistsAndAllFqnsAlreadyLinked_thenNoLinkUpdate() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("charts.json"), |
|
|
|
|
|
"{\"widgetsBundle\":{\"alias\":\"charts\",\"title\":\"Charts\",\"description\":\"d\",\"order\":10}," + |
|
|
|
|
|
"\"widgetTypeFqns\":[\"line_chart\",\"bar_chart\"]}"); |
|
|
|
|
|
|
|
|
|
|
|
WidgetsBundle existingBundle = createTestBundle("charts", "Charts"); |
|
|
|
|
|
existingBundle.setDescription("d"); |
|
|
|
|
|
existingBundle.setOrder(10); |
|
|
|
|
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "charts")).thenReturn(existingBundle); |
|
|
|
|
|
when(widgetTypeService.findWidgetFqnsByWidgetsBundleId(TenantId.SYS_TENANT_ID, existingBundle.getId())) |
|
|
|
|
|
.thenReturn(List.of("line_chart", "bar_chart")); |
|
|
|
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles"); |
|
|
|
|
|
|
|
|
|
|
|
assertEquals(0, updated); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
verify(widgetTypeService, never()).updateWidgetsBundleWidgetFqns(any(), any(), any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenOnlyBundleImageFormatDiffers_thenNoUpdate() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
// File carries a base64 data URI; DB has the resolved system-image URL — same content, different format.
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("charts.json"), |
|
|
|
|
|
"{\"widgetsBundle\":{\"alias\":\"charts\",\"title\":\"Charts\",\"description\":\"d\",\"order\":10," + |
|
|
|
|
|
"\"image\":\"data:image/png;base64,iVBORw0KGgo\"}," + |
|
|
|
|
|
"\"widgetTypeFqns\":[]}"); |
|
|
|
|
|
|
|
|
|
|
|
WidgetsBundle existingBundle = createTestBundle("charts", "Charts"); |
|
|
|
|
|
existingBundle.setDescription("d"); |
|
|
|
|
|
existingBundle.setOrder(10); |
|
|
|
|
|
existingBundle.setImage("tb-image;/api/images/system/charts.png"); |
|
|
|
|
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "charts")).thenReturn(existingBundle); |
|
|
|
|
|
when(widgetTypeService.findWidgetFqnsByWidgetsBundleId(TenantId.SYS_TENANT_ID, existingBundle.getId())) |
|
|
|
|
|
.thenReturn(List.of()); |
|
|
|
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles"); |
|
|
|
|
|
|
|
|
|
|
|
assertEquals(0, updated); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
verify(widgetTypeService, never()).updateWidgetsBundleWidgetFqns(any(), any(), any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenBundleMetadataChanged_thenUpdateBundle() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("charts.json"), |
|
|
|
|
|
"{\"widgetsBundle\":{\"alias\":\"charts\",\"title\":\"New Title\",\"description\":\"new\",\"order\":20}," + |
|
|
|
|
|
"\"widgetTypeFqns\":[\"line_chart\"]}"); |
|
|
|
|
|
|
|
|
|
|
|
WidgetsBundle existingBundle = createTestBundle("charts", "Old Title"); |
|
|
|
|
|
existingBundle.setDescription("old"); |
|
|
|
|
|
existingBundle.setOrder(10); |
|
|
|
|
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "charts")).thenReturn(existingBundle); |
|
|
|
|
|
when(widgetTypeService.findWidgetFqnsByWidgetsBundleId(TenantId.SYS_TENANT_ID, existingBundle.getId())) |
|
|
|
|
|
.thenReturn(List.of("line_chart")); |
|
|
|
|
|
|
|
|
|
|
|
Integer updated = ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles"); |
|
|
|
|
|
|
|
|
|
|
|
assertEquals(1, updated); |
|
|
|
|
|
verify(widgetsBundleService).saveWidgetsBundle(argThat(b -> |
|
|
|
|
|
"New Title".equals(b.getTitle()) && "new".equals(b.getDescription()) && b.getOrder() == 20 |
|
|
|
|
|
)); |
|
|
|
|
|
verify(widgetTypeService, never()).updateWidgetsBundleWidgetFqns(any(), any(), any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenBundleAliasIsBlank_thenThrowException() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("broken.json"), |
|
|
|
|
|
"{\"widgetsBundle\":{\"alias\":\"\",\"title\":\"Broken\"}}"); |
|
|
|
|
|
|
|
|
|
|
|
assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles")); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenBundleJsonMissingWidgetsBundleField_thenThrowException() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("broken.json"), "{\"foo\":\"bar\"}"); |
|
|
|
|
|
|
|
|
|
|
|
assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles")); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
void whenBundleHasInlineWidgetTypes_thenThrowException() throws Exception { |
|
|
|
|
|
Path bundlesDir = tempDir.resolve("widget_bundles"); |
|
|
|
|
|
Files.createDirectories(bundlesDir); |
|
|
|
|
|
when(installScripts.getWidgetBundlesDir()).thenReturn(bundlesDir); |
|
|
|
|
|
|
|
|
|
|
|
Files.writeString(bundlesDir.resolve("charts.json"), |
|
|
|
|
|
"{\"widgetsBundle\":{\"alias\":\"charts\",\"title\":\"Charts\",\"description\":\"d\",\"order\":10}," + |
|
|
|
|
|
"\"widgetTypes\":[" + |
|
|
|
|
|
"{\"fqn\":\"inline_chart\",\"name\":\"Inline\",\"descriptor\":{\"type\":\"latest\"}}" + |
|
|
|
|
|
"]}"); |
|
|
|
|
|
|
|
|
|
|
|
WidgetsBundle existingBundle = createTestBundle("charts", "Charts"); |
|
|
|
|
|
existingBundle.setDescription("d"); |
|
|
|
|
|
existingBundle.setOrder(10); |
|
|
|
|
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "charts")).thenReturn(existingBundle); |
|
|
|
|
|
|
|
|
|
|
|
assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(reconciler, "updateWidgetBundles")); |
|
|
|
|
|
verify(widgetTypeService, never()).saveWidgetType(any()); |
|
|
|
|
|
verify(widgetTypeService, never()).updateWidgetsBundleWidgetFqns(any(), any(), any()); |
|
|
|
|
|
verify(widgetsBundleService, never()).saveWidgetsBundle(any()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private WidgetsBundle createTestBundle(String alias, String title) { |
|
|
|
|
|
WidgetsBundle bundle = new WidgetsBundle(); |
|
|
|
|
|
bundle.setId(new WidgetsBundleId(UUID.randomUUID())); |
|
|
|
|
|
bundle.setAlias(alias); |
|
|
|
|
|
bundle.setTitle(title); |
|
|
|
|
|
bundle.setTenantId(TenantId.SYS_TENANT_ID); |
|
|
|
|
|
return bundle; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|