@ -31,9 +31,12 @@ import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.common.util.JacksonUtil ;
import org.thingsboard.server.common.data.id.TenantId ;
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.WidgetsBundle ;
import org.thingsboard.server.dao.resource.ImageService ;
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.InstallScripts ;
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.Path ;
import java.util.Collections ;
import java.util.List ;
import java.util.Set ;
import java.util.UUID ;
import java.util.concurrent.CountDownLatch ;
@ -81,6 +85,9 @@ public class SystemPatchApplierTest {
@Mock
private WidgetTypeService widgetTypeService ;
@Mock
private WidgetsBundleService widgetsBundleService ;
@Mock
private ImageService imageService ;
@ -155,19 +162,72 @@ public class SystemPatchApplierTest {
}
@Test
void whenWidgetNotFound_thenThrowException ( ) throws Exception {
void whenWidgetNotFound_thenCreateNewWidget ( ) throws Exception {
Path widgetTypesDir = tempDir . resolve ( "widget_types" ) ;
Files . createDirectories ( widgetTypesDir ) ;
when ( installScripts . getWidgetTypesDir ( ) ) . thenReturn ( widgetTypesDir ) ;
WidgetTypeDetails test Widget = createTestWidgetType ( "test_widget" , "Test Widget" ) ;
String json = JacksonUtil . toString ( test Widget) ;
WidgetTypeDetails file Widget = createTestWidgetType ( "new_widget" , "New Widget" ) ;
String json = JacksonUtil . toString ( file Widget) ;
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" ) ) ;
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
@ -189,9 +249,11 @@ public class SystemPatchApplierTest {
when ( widgetTypeService . findWidgetTypeDetailsByTenantIdAndFqn ( TenantId . SYS_TENANT_ID , "test_widget" ) )
. 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 - >
w . getDescriptor ( ) . get ( "version" ) . asInt ( ) = = 2
) ) ;
@ -214,9 +276,11 @@ public class SystemPatchApplierTest {
when ( widgetTypeService . findWidgetTypeDetailsByTenantIdAndFqn ( TenantId . SYS_TENANT_ID , "test_widget" ) )
. 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 ( ) ) ) ) ;
}
@ -237,9 +301,11 @@ public class SystemPatchApplierTest {
when ( widgetTypeService . findWidgetTypeDetailsByTenantIdAndFqn ( TenantId . SYS_TENANT_ID , "test_widget" ) )
. 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 ( ) ) ;
}
@ -339,8 +405,8 @@ public class SystemPatchApplierTest {
// Simulate work while holding lock
Thread . sleep ( 100 ) ;
Integer updated = ReflectionTestUtils . invokeMethod ( reconciler , "updateWidgetTypes" ) ;
firstThreadSavedWidget . set ( updated ! = null & & updated > 0 ) ;
SystemPatchApplier . WidgetTypeStats stats = ReflectionTestUtils . invokeMethod ( reconciler , "updateWidgetTypes" ) ;
firstThreadSavedWidget . set ( stats ! = null & & stats . updated ( ) > 0 ) ;
ReflectionTestUtils . invokeMethod ( reconciler , "releaseAdvisoryLock" ) ;
}
@ -360,8 +426,8 @@ public class SystemPatchApplierTest {
secondThreadAcquiredLock . set ( Boolean . TRUE . equals ( acquired ) ) ;
if ( secondThreadAcquiredLock . get ( ) ) {
Integer updated = ReflectionTestUtils . invokeMethod ( reconciler , "updateWidgetTypes" ) ;
secondThreadSavedWidget . set ( updated ! = null & & updated > 0 ) ;
SystemPatchApplier . WidgetTypeStats stats = ReflectionTestUtils . invokeMethod ( reconciler , "updateWidgetTypes" ) ;
secondThreadSavedWidget . set ( stats ! = null & & stats . updated ( ) > 0 ) ;
ReflectionTestUtils . invokeMethod ( reconciler , "releaseAdvisoryLock" ) ;
}
@ -560,6 +626,7 @@ public class SystemPatchApplierTest {
Path widgetTypesDir = tempDir . resolve ( "widget_types" ) ;
Files . createDirectories ( widgetTypesDir ) ;
when ( installScripts . getWidgetTypesDir ( ) ) . thenReturn ( widgetTypesDir ) ;
when ( installScripts . getWidgetBundlesDir ( ) ) . thenReturn ( tempDir . resolve ( "widget_bundles_missing" ) ) ;
ReflectionTestUtils . invokeMethod ( reconciler , "applyPatchIfNeeded" ) ;
@ -607,6 +674,7 @@ public class SystemPatchApplierTest {
Path widgetTypesDir = tempDir . resolve ( "widget_types" ) ;
Files . createDirectories ( widgetTypesDir ) ;
when ( installScripts . getWidgetTypesDir ( ) ) . thenReturn ( widgetTypesDir ) ;
when ( installScripts . getWidgetBundlesDir ( ) ) . thenReturn ( tempDir . resolve ( "widget_bundles_missing" ) ) ;
ReflectionTestUtils . invokeMethod ( reconciler , "applyPatchIfNeeded" ) ;
@ -834,6 +902,7 @@ public class SystemPatchApplierTest {
Path widgetTypesDir = tempDir . resolve ( "widget_types" ) ;
Files . createDirectories ( 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 ( ) ) ;
@ -854,4 +923,201 @@ public class SystemPatchApplierTest {
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 ;
}
}