1+ /*
2+ *
3+ * Copyright (c) 2018-2025 Green Button Alliance, Inc.
4+ *
5+ * Portions (c) 2013-2018 EnergyOS.org
6+ *
7+ * Licensed under the Apache License, Version 2.0 (the "License");
8+ * you may not use this file except in compliance with the License.
9+ * You may obtain a copy of the License at
10+ *
11+ * http://www.apache.org/licenses/LICENSE-2.0
12+ *
13+ * Unless required by applicable law or agreed to in writing, software
14+ * distributed under the License is distributed on an "AS IS" BASIS,
15+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+ * See the License for the specific language governing permissions and
17+ * limitations under the License.
18+ *
19+ */
20+
21+ package org .greenbuttonalliance .espi .common .mapper ;
22+
23+ import org .greenbuttonalliance .espi .common .domain .DateTimeInterval ;
24+ import org .greenbuttonalliance .espi .common .domain .ServiceCategory ;
25+ import org .greenbuttonalliance .espi .common .domain .usage .*;
26+ import org .greenbuttonalliance .espi .common .dto .usage .*;
27+ import org .greenbuttonalliance .espi .common .mapper .usage .*;
28+ import org .junit .jupiter .api .BeforeEach ;
29+ import org .junit .jupiter .api .Test ;
30+ import org .mapstruct .factory .Mappers ;
31+
32+ import java .time .OffsetDateTime ;
33+ import java .util .Arrays ;
34+
35+ import static org .assertj .core .api .Assertions .assertThat ;
36+
37+ /**
38+ * Integration test for all MapStruct mappers working together.
39+ *
40+ * Tests the complete Entity-DTO mapping pipeline with full Green Button
41+ * data structures including all nested relationships.
42+ */
43+ class MapperIntegrationTest {
44+
45+ private UsagePointMapper usagePointMapper ;
46+
47+ @ BeforeEach
48+ void setUp () {
49+ // Initialize the complete mapper chain
50+ usagePointMapper = Mappers .getMapper (UsagePointMapper .class );
51+ }
52+
53+ @ Test
54+ void testCompleteGreenButtonDataStructure_EntityToDto () {
55+ // Create a complete Green Button data structure
56+ UsagePointEntity usagePoint = createCompleteGreenButtonStructure ();
57+
58+ // Convert the entire structure to DTO
59+ UsagePointDto usagePointDto = usagePointMapper .toDto (usagePoint );
60+
61+ // Verify the complete structure was mapped correctly
62+ verifyCompleteStructureMapping (usagePointDto );
63+ }
64+
65+ @ Test
66+ void testCompleteGreenButtonDataStructure_DtoToEntity () {
67+ // Create a complete Green Button DTO structure
68+ UsagePointDto usagePointDto = createCompleteGreenButtonDtoStructure ();
69+
70+ // Convert the entire structure to Entity
71+ UsagePointEntity usagePointEntity = usagePointMapper .toEntity (usagePointDto );
72+
73+ // Verify the complete structure was mapped correctly
74+ verifyCompleteEntityMapping (usagePointEntity );
75+ }
76+
77+ @ Test
78+ void testRoundTripMapping_DataIntegrity () {
79+ // Create original entity
80+ UsagePointEntity originalEntity = createCompleteGreenButtonStructure ();
81+
82+ // Round trip: Entity -> DTO -> Entity
83+ UsagePointDto dto = usagePointMapper .toDto (originalEntity );
84+ UsagePointEntity roundTripEntity = usagePointMapper .toEntity (dto );
85+
86+ // Verify core data integrity (ignoring JPA-specific fields)
87+ assertDataIntegrityAfterRoundTrip (originalEntity , roundTripEntity );
88+ }
89+
90+ @ Test
91+ void testMapperChain_WithNullValues () {
92+ // Test null safety throughout the mapper chain
93+ UsagePointEntity entity = new UsagePointEntity ();
94+ entity .setUuid ("null-test-uuid" );
95+ entity .setMeterReadings (null );
96+ entity .setUsageSummaries (null );
97+ entity .setElectricPowerQualitySummaries (null );
98+
99+ // Should not throw exceptions
100+ UsagePointDto dto = usagePointMapper .toDto (entity );
101+ UsagePointEntity backToEntity = usagePointMapper .toEntity (dto );
102+
103+ assertThat (dto .uuid ()).isEqualTo ("null-test-uuid" );
104+ assertThat (backToEntity .getUuid ()).isEqualTo ("null-test-uuid" );
105+ }
106+
107+ @ Test
108+ void testMapperPerformance_LargeDataSet () {
109+ // Create a large data set for performance testing
110+ UsagePointEntity largeEntity = createLargeGreenButtonStructure ();
111+
112+ // Time the complete conversion
113+ long startTime = System .currentTimeMillis ();
114+ UsagePointDto dto = usagePointMapper .toDto (largeEntity );
115+ long conversionTime = System .currentTimeMillis () - startTime ;
116+
117+ // Verify performance and correctness
118+ assertThat (dto ).isNotNull ();
119+ assertThat (dto .meterReadings ()).hasSize (50 );
120+ assertThat (conversionTime ).isLessThan (5000 ); // Should complete in under 5 seconds
121+
122+ // Verify deep structure was mapped
123+ assertThat (dto .meterReadings ().get (0 ).intervalBlocks ()).hasSize (10 );
124+ assertThat (dto .meterReadings ().get (0 ).intervalBlocks ().get (0 ).intervalReadings ()).hasSize (50 );
125+ }
126+
127+ private UsagePointEntity createCompleteGreenButtonStructure () {
128+ UsagePointEntity usagePoint = new UsagePointEntity ();
129+ usagePoint .setId (1L );
130+ usagePoint .setUuid ("complete-usage-point-uuid" );
131+ usagePoint .setDescription ("Complete Green Button Usage Point" );
132+ usagePoint .setServiceCategory (ServiceCategory .ELECTRICITY_SERVICE );
133+ usagePoint .setStatus ((short ) 1 );
134+ usagePoint .setPublished (OffsetDateTime .now ());
135+ usagePoint .setUpdated (OffsetDateTime .now ());
136+
137+ // Create meter reading with full structure
138+ MeterReadingEntity meterReading = createCompleteMeterReading ();
139+ usagePoint .setMeterReadings (Arrays .asList (meterReading ));
140+
141+ // Create usage summary
142+ UsageSummaryEntity usageSummary = createCompleteUsageSummary ();
143+ usagePoint .setUsageSummaries (Arrays .asList (usageSummary ));
144+
145+ // Create electric power quality summary
146+ ElectricPowerQualitySummaryEntity epqs = createCompleteElectricPowerQualitySummary ();
147+ usagePoint .setElectricPowerQualitySummaries (Arrays .asList (epqs ));
148+
149+ return usagePoint ;
150+ }
151+
152+ private MeterReadingEntity createCompleteMeterReading () {
153+ MeterReadingEntity meterReading = new MeterReadingEntity ();
154+ meterReading .setId (2L );
155+ meterReading .setUuid ("complete-meter-reading-uuid" );
156+ meterReading .setDescription ("Complete Meter Reading" );
157+ meterReading .setPublished (OffsetDateTime .now ());
158+ meterReading .setUpdated (OffsetDateTime .now ());
159+
160+ // Create reading type
161+ ReadingTypeEntity readingType = new ReadingTypeEntity ();
162+ readingType .setId (3L );
163+ readingType .setUuid ("complete-reading-type-uuid" );
164+ readingType .setDescription ("Complete Reading Type" );
165+ readingType .setKind (12 ); // Energy
166+ readingType .setUom (72 ); // Wh
167+ readingType .setPowerOfTenMultiplier (3 ); // kilo
168+ meterReading .setReadingType (readingType );
169+
170+ // Create interval block with readings
171+ IntervalBlockEntity intervalBlock = createCompleteIntervalBlock ();
172+ meterReading .setIntervalBlocks (Arrays .asList (intervalBlock ));
173+
174+ return meterReading ;
175+ }
176+
177+ private IntervalBlockEntity createCompleteIntervalBlock () {
178+ IntervalBlockEntity intervalBlock = new IntervalBlockEntity ();
179+ intervalBlock .setId (4L );
180+ intervalBlock .setUuid ("complete-interval-block-uuid" );
181+ intervalBlock .setDescription ("Complete Interval Block" );
182+
183+ DateTimeInterval interval = new DateTimeInterval ();
184+ interval .setStart (1609459200L );
185+ interval .setDuration (3600L );
186+ intervalBlock .setInterval (interval );
187+
188+ // Create interval readings with quality
189+ IntervalReadingEntity reading1 = createCompleteIntervalReading ("reading-1" , 1000L );
190+ IntervalReadingEntity reading2 = createCompleteIntervalReading ("reading-2" , 1200L );
191+ intervalBlock .setIntervalReadings (Arrays .asList (reading1 , reading2 ));
192+
193+ return intervalBlock ;
194+ }
195+
196+ private IntervalReadingEntity createCompleteIntervalReading (String uuidSuffix , Long value ) {
197+ IntervalReadingEntity reading = new IntervalReadingEntity ();
198+ reading .setId (5L );
199+ reading .setUuid ("complete-interval-reading-" + uuidSuffix );
200+ reading .setValue (value );
201+ reading .setCost (value / 20 ); // Cost is 5% of value
202+ reading .setCurrency (840 ); // USD
203+ reading .setConsumptionTier (1 );
204+ reading .setTou (0 );
205+ reading .setCpp (0 );
206+
207+ DateTimeInterval timePeriod = new DateTimeInterval ();
208+ timePeriod .setStart (1609459200L );
209+ timePeriod .setDuration (900L ); // 15 minutes
210+ reading .setTimePeriod (timePeriod );
211+
212+ // Add reading qualities
213+ ReadingQualityEntity quality = new ReadingQualityEntity ();
214+ quality .setId (6L );
215+ quality .setUuid ("quality-" + uuidSuffix );
216+ quality .setQuality ("VALID" );
217+ reading .setReadingQualities (Arrays .asList (quality ));
218+
219+ return reading ;
220+ }
221+
222+ private UsageSummaryEntity createCompleteUsageSummary () {
223+ UsageSummaryEntity usageSummary = new UsageSummaryEntity ();
224+ usageSummary .setId (7L );
225+ usageSummary .setUuid ("complete-usage-summary-uuid" );
226+ usageSummary .setDescription ("Complete Usage Summary" );
227+ usageSummary .setBillToDate (15000L );
228+ usageSummary .setBillLastPeriod (14000L );
229+ usageSummary .setCurrency (840 ); // USD
230+
231+ DateTimeInterval billingPeriod = new DateTimeInterval ();
232+ billingPeriod .setStart (1609459200L );
233+ billingPeriod .setDuration (2592000L ); // 30 days
234+ usageSummary .setBillingPeriod (billingPeriod );
235+
236+ return usageSummary ;
237+ }
238+
239+ private ElectricPowerQualitySummaryEntity createCompleteElectricPowerQualitySummary () {
240+ ElectricPowerQualitySummaryEntity epqs = new ElectricPowerQualitySummaryEntity ();
241+ epqs .setId (8L );
242+ epqs .setUuid ("complete-epqs-uuid" );
243+ epqs .setDescription ("Complete Electric Power Quality Summary" );
244+ epqs .setFlickerPlt (0.5f );
245+ epqs .setFlickerPst (0.3f );
246+ epqs .setHarmonicVoltage (2.1f );
247+ epqs .setLongInterruptions (0L );
248+ epqs .setMainsVoltage (240.0f );
249+ epqs .setPowerFrequency (60.0f );
250+ epqs .setShortInterruptions (1L );
251+
252+ DateTimeInterval summaryInterval = new DateTimeInterval ();
253+ summaryInterval .setStart (1609459200L );
254+ summaryInterval .setDuration (86400L ); // 24 hours
255+ epqs .setSummaryInterval (summaryInterval );
256+
257+ return epqs ;
258+ }
259+
260+ private UsagePointDto createCompleteGreenButtonDtoStructure () {
261+ MeterReadingDto meterReading = new MeterReadingDto (
262+ null , "dto-meter-reading-uuid" , OffsetDateTime .now (), OffsetDateTime .now (),
263+ null , null , null , "DTO Meter Reading" ,
264+ new ReadingTypeDto (), Arrays .asList (new IntervalBlockDto ())
265+ );
266+
267+ return new UsagePointDto (
268+ "dto-usage-point-uuid" ,
269+ "DTO Usage Point" ,
270+ new byte []{1 , 2 , 3 , 4 },
271+ ServiceCategory .ELECTRICITY_SERVICE ,
272+ (short ) 1 ,
273+ new ServiceDeliveryPointDto (),
274+ Arrays .asList (meterReading ),
275+ Arrays .asList (new UsageSummaryDto ()),
276+ Arrays .asList (new ElectricPowerQualitySummaryDto ())
277+ );
278+ }
279+
280+ private UsagePointEntity createLargeGreenButtonStructure () {
281+ UsagePointEntity usagePoint = new UsagePointEntity ();
282+ usagePoint .setUuid ("large-structure-uuid" );
283+ usagePoint .setDescription ("Large Green Button Structure" );
284+ usagePoint .setServiceCategory (ServiceCategory .ELECTRICITY_SERVICE );
285+
286+ // Create 50 meter readings, each with 10 interval blocks, each with 50 readings
287+ MeterReadingEntity [] meterReadings = new MeterReadingEntity [50 ];
288+ for (int i = 0 ; i < 50 ; i ++) {
289+ meterReadings [i ] = createLargeMeterReading ("large-meter-" + i );
290+ }
291+ usagePoint .setMeterReadings (Arrays .asList (meterReadings ));
292+
293+ return usagePoint ;
294+ }
295+
296+ private MeterReadingEntity createLargeMeterReading (String uuid ) {
297+ MeterReadingEntity meterReading = new MeterReadingEntity ();
298+ meterReading .setUuid (uuid );
299+ meterReading .setDescription ("Large Meter Reading: " + uuid );
300+
301+ // Create 10 interval blocks
302+ IntervalBlockEntity [] blocks = new IntervalBlockEntity [10 ];
303+ for (int i = 0 ; i < 10 ; i ++) {
304+ blocks [i ] = createLargeIntervalBlock (uuid + "-block-" + i );
305+ }
306+ meterReading .setIntervalBlocks (Arrays .asList (blocks ));
307+
308+ return meterReading ;
309+ }
310+
311+ private IntervalBlockEntity createLargeIntervalBlock (String uuid ) {
312+ IntervalBlockEntity block = new IntervalBlockEntity ();
313+ block .setUuid (uuid );
314+
315+ DateTimeInterval interval = new DateTimeInterval ();
316+ interval .setStart (1609459200L );
317+ interval .setDuration (3600L );
318+ block .setInterval (interval );
319+
320+ // Create 50 interval readings
321+ IntervalReadingEntity [] readings = new IntervalReadingEntity [50 ];
322+ for (int i = 0 ; i < 50 ; i ++) {
323+ readings [i ] = new IntervalReadingEntity ();
324+ readings [i ].setUuid (uuid + "-reading-" + i );
325+ readings [i ].setValue (1000L + i );
326+ }
327+ block .setIntervalReadings (Arrays .asList (readings ));
328+
329+ return block ;
330+ }
331+
332+ private void verifyCompleteStructureMapping (UsagePointDto dto ) {
333+ assertThat (dto .uuid ()).isEqualTo ("complete-usage-point-uuid" );
334+ assertThat (dto .description ()).isEqualTo ("Complete Green Button Usage Point" );
335+ assertThat (dto .serviceCategory ()).isEqualTo (ServiceCategory .ELECTRICITY_SERVICE );
336+
337+ // Verify meter readings
338+ assertThat (dto .meterReadings ()).hasSize (1 );
339+ MeterReadingDto meterReading = dto .meterReadings ().get (0 );
340+ assertThat (meterReading .uuid ()).isEqualTo ("complete-meter-reading-uuid" );
341+
342+ // Verify nested structure
343+ assertThat (meterReading .intervalBlocks ()).hasSize (1 );
344+ IntervalBlockDto intervalBlock = meterReading .intervalBlocks ().get (0 );
345+ assertThat (intervalBlock .intervalReadings ()).hasSize (2 );
346+
347+ // Verify usage summaries
348+ assertThat (dto .usageSummaries ()).hasSize (1 );
349+ assertThat (dto .usageSummaries ().get (0 ).billToDate ()).isEqualTo (15000L );
350+
351+ // Verify power quality summaries
352+ assertThat (dto .electricPowerQualitySummaries ()).hasSize (1 );
353+ assertThat (dto .electricPowerQualitySummaries ().get (0 ).flickerPlt ()).isEqualTo (0.5f );
354+ }
355+
356+ private void verifyCompleteEntityMapping (UsagePointEntity entity ) {
357+ assertThat (entity .getUuid ()).isEqualTo ("dto-usage-point-uuid" );
358+ assertThat (entity .getDescription ()).isEqualTo ("DTO Usage Point" );
359+ assertThat (entity .getServiceCategory ()).isEqualTo (ServiceCategory .ELECTRICITY_SERVICE );
360+
361+ // Verify JPA fields are properly ignored
362+ assertThat (entity .getId ()).isNull ();
363+ assertThat (entity .getRelatedLinks ()).isNull ();
364+
365+ // Verify collections mapped
366+ assertThat (entity .getMeterReadings ()).hasSize (1 );
367+ assertThat (entity .getUsageSummaries ()).hasSize (1 );
368+ assertThat (entity .getElectricPowerQualitySummaries ()).hasSize (1 );
369+ }
370+
371+ private void assertDataIntegrityAfterRoundTrip (UsagePointEntity original , UsagePointEntity roundTrip ) {
372+ // Core fields should match
373+ assertThat (roundTrip .getUuid ()).isEqualTo (original .getUuid ());
374+ assertThat (roundTrip .getDescription ()).isEqualTo (original .getDescription ());
375+ assertThat (roundTrip .getServiceCategory ()).isEqualTo (original .getServiceCategory ());
376+ assertThat (roundTrip .getStatus ()).isEqualTo (original .getStatus ());
377+
378+ // Collection sizes should match
379+ assertThat (roundTrip .getMeterReadings ()).hasSameSizeAs (original .getMeterReadings ());
380+ assertThat (roundTrip .getUsageSummaries ()).hasSameSizeAs (original .getUsageSummaries ());
381+ assertThat (roundTrip .getElectricPowerQualitySummaries ())
382+ .hasSameSizeAs (original .getElectricPowerQualitySummaries ());
383+
384+ // JPA IDs should be null (not preserved in DTO round trip)
385+ assertThat (roundTrip .getId ()).isNull ();
386+ }
387+ }
0 commit comments