1414
1515import static org .junit .jupiter .api .Assertions .assertEquals ;
1616import static org .junit .jupiter .api .Assertions .assertNotNull ;
17+ import static org .junit .jupiter .api .Assertions .fail ;
1718import static org .mockito .Mockito .when ;
1819
1920import java .time .ZoneId ;
2425import org .junit .jupiter .api .BeforeEach ;
2526import org .junit .jupiter .api .Test ;
2627import org .junit .jupiter .api .extension .ExtendWith ;
28+ import org .junit .jupiter .params .ParameterizedTest ;
29+ import org .junit .jupiter .params .provider .ValueSource ;
2730import org .mockito .Mock ;
2831import org .mockito .junit .jupiter .MockitoExtension ;
2932import org .openhab .core .items .ItemRegistry ;
3437import org .openhab .core .persistence .FilterCriteria ;
3538import org .openhab .core .persistence .HistoricItem ;
3639import org .openhab .core .persistence .PersistedItem ;
40+ import org .slf4j .Logger ;
41+ import org .slf4j .LoggerFactory ;
3742
3843/**
3944 * Tests for {@link RRD4jPersistenceService}.
4348 */
4449@ ExtendWith (MockitoExtension .class )
4550class RRD4jPersistenceServiceTest {
46- static final int STORAGE_TIMEOUT_MS = 1000 ;
51+ private static final long STORAGE_TIMEOUT_MS = 20000 ; // 20 seconds for CI
52+ private static final long POLL_INTERVAL_MS = 250 ; // Check every 250ms
53+
54+ private final Logger logger = LoggerFactory .getLogger (RRD4jPersistenceServiceTest .class );
4755
4856 @ Mock
4957 private ItemRegistry itemRegistry ;
@@ -62,19 +70,58 @@ void setUp() throws Exception {
6270 service = new RRD4jPersistenceService (itemRegistry , Map .of ());
6371 }
6472
65- private void configureNumberItem () throws Exception {
66- when (numberItem .getName ()).thenReturn ("TestNumber" );
73+ /**
74+ * Waits for data to be persisted by polling the database.
75+ * This is more robust than Thread.sleep() in CI environments with resource contention.
76+ *
77+ * @param itemName the name of the item to check
78+ * @param timeoutMs maximum time to wait in milliseconds
79+ * @throws InterruptedException if interrupted while waiting
80+ */
81+ private void waitForStorage (String itemName , long timeoutMs ) throws InterruptedException {
82+ long startTime = System .currentTimeMillis ();
83+ int attempts = 0 ;
84+
85+ while (System .currentTimeMillis () - startTime < timeoutMs ) {
86+ attempts ++;
87+
88+ FilterCriteria criteria = new FilterCriteria ();
89+ criteria .setItemName (itemName );
90+ criteria .setPageSize (1 );
91+
92+ try {
93+ Iterable <HistoricItem > results = service .query (criteria );
94+ if (results .iterator ().hasNext ()) {
95+ long elapsed = System .currentTimeMillis () - startTime ;
96+ logger .info ("Storage completed for '{}' after {}ms ({} attempts)" , itemName , elapsed , attempts );
97+ return ; // Success!
98+ }
99+ } catch (Exception e ) {
100+ // Query might fail if data not ready yet, continue polling
101+ logger .info ("Query attempt {} failed: {}" , attempts , e .getMessage ());
102+ }
103+
104+ Thread .sleep (POLL_INTERVAL_MS );
105+ }
106+
107+ long elapsed = System .currentTimeMillis () - startTime ;
108+ fail (String .format ("Data for item '%s' was not persisted within %dms (%d polling attempts)." , itemName , elapsed ,
109+ attempts ));
110+ }
111+
112+ private void configureNumberItem (String suffix ) throws Exception {
113+ when (numberItem .getName ()).thenReturn ("TestNumber" + suffix );
67114 when (numberItem .getType ()).thenReturn ("Number" );
68115 when (numberItem .getState ()).thenReturn (new DecimalType (42.5 ));
69116 when (numberItem .getStateAs (DecimalType .class )).thenReturn (new DecimalType (42.5 ));
70- when (itemRegistry .getItem ("TestNumber" )).thenReturn (numberItem );
117+ when (itemRegistry .getItem ("TestNumber" + suffix )).thenReturn (numberItem );
71118 }
72119
73- private void configureSwitchItem () throws Exception {
74- when (switchItem .getName ()).thenReturn ("TestSwitch" );
120+ private void configureSwitchItem (String suffix ) throws Exception {
121+ when (switchItem .getName ()).thenReturn ("TestSwitch" + suffix );
75122 when (switchItem .getType ()).thenReturn ("Switch" );
76123 when (switchItem .getStateAs (DecimalType .class )).thenReturn (new DecimalType (1 ));
77- when (itemRegistry .getItem ("TestSwitch" )).thenReturn (switchItem );
124+ when (itemRegistry .getItem ("TestSwitch" + suffix )).thenReturn (switchItem );
78125 }
79126
80127 @ AfterEach
@@ -84,19 +131,25 @@ void tearDown() throws Exception {
84131 }
85132 }
86133
87- @ Test
88- void storeAndRetrieveNumberValue () throws Exception {
89- configureNumberItem ();
134+ @ ParameterizedTest
135+ @ ValueSource (booleans = { true , false })
136+ void storeAndRetrieveNumberValue (boolean reloadAfterStore ) throws Exception {
137+ configureNumberItem (reloadAfterStore ? "_PERSISTED" : "_MEMORY" );
90138
91139 // Store a value
92140 service .store (numberItem );
93141
142+ if (reloadAfterStore ) {
143+ service .deactivate ();
144+ service = new RRD4jPersistenceService (itemRegistry , Map .of ());
145+ }
146+
94147 // Wait for background storage to complete
95- Thread . sleep ( STORAGE_TIMEOUT_MS );
148+ waitForStorage ( numberItem . getName (), STORAGE_TIMEOUT_MS );
96149
97150 // Query the value back
98151 FilterCriteria criteria = new FilterCriteria ();
99- criteria .setItemName ("TestNumber" );
152+ criteria .setItemName (numberItem . getName () );
100153 criteria .setOrdering (FilterCriteria .Ordering .DESCENDING );
101154 criteria .setPageSize (1 );
102155 criteria .setPageNumber (0 );
@@ -107,23 +160,29 @@ void storeAndRetrieveNumberValue() throws Exception {
107160 // Verify the retrieved value
108161 HistoricItem item = results .iterator ().next ();
109162 assertNotNull (item );
110- assertEquals ("TestNumber" , item .getName ());
163+ assertEquals (numberItem . getName () , item .getName ());
111164 assertEquals (new DecimalType (42.5 ), item .getState ());
112165 }
113166
114- @ Test
115- void storeAndRetrieveSwitchValue () throws Exception {
116- configureSwitchItem ();
167+ @ ParameterizedTest
168+ @ ValueSource (booleans = { true , false })
169+ void storeAndRetrieveSwitchValue (boolean reloadAfterStore ) throws Exception {
170+ configureSwitchItem (reloadAfterStore ? "_PERSISTED" : "_MEMORY" );
117171
118172 // Store a value
119173 service .store (switchItem );
120174
175+ if (reloadAfterStore ) {
176+ service .deactivate ();
177+ service = new RRD4jPersistenceService (itemRegistry , Map .of ());
178+ }
179+
121180 // Wait for background storage to complete
122- Thread . sleep ( STORAGE_TIMEOUT_MS );
181+ waitForStorage ( switchItem . getName (), STORAGE_TIMEOUT_MS );
123182
124183 // Query the value back
125184 FilterCriteria criteria = new FilterCriteria ();
126- criteria .setItemName ("TestSwitch" );
185+ criteria .setItemName (switchItem . getName () );
127186 criteria .setOrdering (FilterCriteria .Ordering .DESCENDING );
128187 criteria .setPageSize (1 );
129188 criteria .setPageNumber (0 );
@@ -134,28 +193,34 @@ void storeAndRetrieveSwitchValue() throws Exception {
134193 // Verify the retrieved value (converted back to OnOffType by toStateMapper)
135194 HistoricItem item = results .iterator ().next ();
136195 assertNotNull (item );
137- assertEquals ("TestSwitch" , item .getName ());
196+ assertEquals (switchItem . getName () , item .getName ());
138197 assertEquals (OnOffType .ON , item .getState ());
139198
140- PersistedItem persistedItem = service .persistedItem ("TestSwitch" , null );
199+ PersistedItem persistedItem = service .persistedItem (switchItem . getName () , null );
141200 assertNotNull (persistedItem );
142- assertEquals ("TestSwitch" , persistedItem .getName ());
201+ assertEquals (switchItem . getName () , persistedItem .getName ());
143202 assertEquals (OnOffType .ON , persistedItem .getState ());
144203 }
145204
146- @ Test
147- void queryWithTimeRange () throws Exception {
148- configureNumberItem ();
205+ @ ParameterizedTest
206+ @ ValueSource (booleans = { true , false })
207+ void queryWithTimeRange (boolean reloadAfterStore ) throws Exception {
208+ configureNumberItem (reloadAfterStore ? "_PERSISTED" : "_MEMORY" );
149209
150210 // Store a value
151211 service .store (numberItem );
152212
213+ if (reloadAfterStore ) {
214+ service .deactivate ();
215+ service = new RRD4jPersistenceService (itemRegistry , Map .of ());
216+ }
217+
153218 // Wait for background storage to complete
154- Thread . sleep ( STORAGE_TIMEOUT_MS );
219+ waitForStorage ( numberItem . getName (), STORAGE_TIMEOUT_MS );
155220
156221 // Query with time range
157222 FilterCriteria criteria = new FilterCriteria ();
158- criteria .setItemName ("TestNumber" );
223+ criteria .setItemName (numberItem . getName () );
159224 criteria .setBeginDate (ZonedDateTime .now (ZoneId .systemDefault ()).minusHours (1 ));
160225 criteria .setEndDate (ZonedDateTime .now (ZoneId .systemDefault ()).plusHours (1 ));
161226 criteria .setOrdering (FilterCriteria .Ordering .ASCENDING );
@@ -188,21 +253,27 @@ void labelIsCorrect() throws Exception {
188253 }
189254
190255 // just to increase test coverage, supply an invalid DB config which will be ignored
191- @ Test
192- void storeAndRetrieveWithInvalidDBConfig () throws Exception {
256+ @ ParameterizedTest
257+ @ ValueSource (booleans = { true , false })
258+ void storeAndRetrieveWithInvalidDBConfig (boolean reloadAfterStore ) throws Exception {
193259 service = new RRD4jPersistenceService (itemRegistry , Map .of ("something.invalid" , "invalid/path/to/db" ));
194260
195- configureNumberItem ();
261+ configureNumberItem (reloadAfterStore ? "_PERSISTED" : "_MEMORY" );
196262
197263 // Store a value
198264 service .store (numberItem );
199265
266+ if (reloadAfterStore ) {
267+ service .deactivate ();
268+ service = new RRD4jPersistenceService (itemRegistry , Map .of ("something.invalid" , "invalid/path/to/db" ));
269+ }
270+
200271 // Wait for background storage to complete
201- Thread . sleep ( STORAGE_TIMEOUT_MS );
272+ waitForStorage ( numberItem . getName (), STORAGE_TIMEOUT_MS );
202273
203274 // Query the value back
204275 FilterCriteria criteria = new FilterCriteria ();
205- criteria .setItemName ("TestNumber" );
276+ criteria .setItemName (numberItem . getName () );
206277 criteria .setOrdering (FilterCriteria .Ordering .ASCENDING );
207278 criteria .setPageSize (1 );
208279 criteria .setPageNumber (0 );
@@ -214,7 +285,7 @@ void storeAndRetrieveWithInvalidDBConfig() throws Exception {
214285 // Verify the retrieved value
215286 HistoricItem item = results .iterator ().next ();
216287 assertNotNull (item );
217- assertEquals ("TestNumber" , item .getName ());
288+ assertEquals (numberItem . getName () , item .getName ());
218289 assertEquals (new DecimalType (42.5 ), item .getState ());
219290 }
220291
0 commit comments