Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit b8db9c3

Browse files
dfcoffinclaude
andcommitted
Step 2: Test mapping logic with complex nested relationships
- Add comprehensive test suite for MapStruct mappers - Test complex nested Entity ↔ DTO conversion scenarios - UsagePointMapperTest: Tests deep object graphs with meter readings, interval blocks - MeterReadingMapperTest: Tests multiple interval blocks and reading qualities - MapperIntegrationTest: Tests complete Green Button data structures - Performance tests for large data sets (50 meter readings × 10 blocks × 50 readings) - Round-trip mapping tests to verify data integrity - Null safety tests throughout mapper chain - Bidirectional conversion validation - Tests ignore JPA-specific fields during DTO mapping 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 07269e0 commit b8db9c3

File tree

3 files changed

+964
-0
lines changed

3 files changed

+964
-0
lines changed
Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
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

Comments
 (0)