Skip to content

Commit

Permalink
NET-422: add test for AlsoEnergy data values response processing; fix…
Browse files Browse the repository at this point in the history
… missing serial/deviceType metadata.
  • Loading branch information
msqr committed Nov 24, 2024
1 parent dd6fdb1 commit 02662c0
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,13 @@ private List<CloudDataValue> sites(CloudIntegrationConfiguration integration) {
private List<CloudDataValue> siteHardware(CloudIntegrationConfiguration integration,
Map<String, ?> filters) {
return restOpsHelper.httpGet("List sites", integration, JsonNode.class,
// @formatter:off
(req) -> fromUri(resolveBaseUrl(integration, AlsoEnergyCloudIntegrationService.BASE_URI))
.path(SITE_HARDWARE_URL_TEMPLATE).queryParam("includeArchivedFields", true)
.path(SITE_HARDWARE_URL_TEMPLATE)
.queryParam("includeArchivedFields", true)
.queryParam("includeDeviceConfig", true)
.buildAndExpand(filters).toUri(),
// @formatter:on
res -> parseSiteHardware(res.getBody(), filters));
}

Expand Down Expand Up @@ -457,22 +461,21 @@ private static List<CloudDataValue> parseSiteHardware(JsonNode json, Map<String,
*/
final String siteId = filters.get(SITE_ID_FILTER).toString();
final var result = new ArrayList<CloudDataValue>(4);
for ( JsonNode meterNode : json.path("hardware") ) {
final JsonNode fieldsNode = meterNode.path("fieldsArchived");
for ( JsonNode deviceNode : json.path("hardware") ) {
final JsonNode fieldsNode = deviceNode.path("fieldsArchived");
if ( !(fieldsNode.isArray() && fieldsNode.size() > 0) ) {
continue;
}
final String id = meterNode.path("id").asText().trim();
final String id = deviceNode.path("id").asText().trim();
if ( id.isEmpty() ) {
continue;
}
final String name = meterNode.path("name").asText().trim();
final String name = deviceNode.path("name").asText().trim();
final var meta = new LinkedHashMap<String, Object>(4);
populateNonEmptyValue(meterNode, "functionCode", "functionCode", meta);
for ( JsonNode configNode : meterNode.path("config") ) {
populateNonEmptyValue(configNode, "serialNumber", DEVICE_SERIAL_NUMBER_METADATA, meta);
populateNonEmptyValue(configNode, "deviceType", "deviceType", meta);
}
populateNonEmptyValue(deviceNode, "functionCode", "functionCode", meta);
JsonNode configNode = deviceNode.path("config");
populateNonEmptyValue(configNode, "serialNumber", DEVICE_SERIAL_NUMBER_METADATA, meta);
populateNonEmptyValue(configNode, "deviceType", "deviceType", meta);

List<CloudDataValue> fields = new ArrayList<>(fieldsNode.size());
for ( JsonNode fieldNode : fieldsNode ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static java.time.ZoneOffset.UTC;
import static java.time.temporal.ChronoUnit.MINUTES;
import static net.solarnetwork.central.c2c.biz.impl.AlsoEnergyCloudDatumStreamService.BIN_DATA_URL;
import static net.solarnetwork.central.c2c.biz.impl.AlsoEnergyCloudDatumStreamService.SITE_HARDWARE_URL_TEMPLATE;
import static net.solarnetwork.central.c2c.biz.impl.AlsoEnergyCloudIntegrationService.BASE_URI;
import static net.solarnetwork.central.c2c.biz.impl.AlsoEnergyFieldFunction.Avg;
import static net.solarnetwork.central.c2c.biz.impl.AlsoEnergyFieldFunction.Last;
Expand All @@ -36,6 +37,7 @@
import static net.solarnetwork.util.DateUtils.ISO_DATE_OPT_TIME_OPT_MILLIS_UTC;
import static org.assertj.core.api.BDDAssertions.and;
import static org.assertj.core.api.BDDAssertions.from;
import static org.assertj.core.api.InstanceOfAssertFactories.list;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
Expand Down Expand Up @@ -75,12 +77,14 @@
import net.solarnetwork.central.c2c.biz.CloudIntegrationsExpressionService;
import net.solarnetwork.central.c2c.biz.impl.AlsoEnergyCloudDatumStreamService;
import net.solarnetwork.central.c2c.biz.impl.AlsoEnergyCloudIntegrationService;
import net.solarnetwork.central.c2c.biz.impl.AlsoEnergyFieldFunction;
import net.solarnetwork.central.c2c.biz.impl.BaseCloudDatumStreamService;
import net.solarnetwork.central.c2c.biz.impl.BasicCloudIntegrationsExpressionService;
import net.solarnetwork.central.c2c.dao.CloudDatumStreamConfigurationDao;
import net.solarnetwork.central.c2c.dao.CloudDatumStreamMappingConfigurationDao;
import net.solarnetwork.central.c2c.dao.CloudDatumStreamPropertyConfigurationDao;
import net.solarnetwork.central.c2c.dao.CloudIntegrationConfigurationDao;
import net.solarnetwork.central.c2c.domain.CloudDataValue;
import net.solarnetwork.central.c2c.domain.CloudDatumStreamConfiguration;
import net.solarnetwork.central.c2c.domain.CloudDatumStreamMappingConfiguration;
import net.solarnetwork.central.c2c.domain.CloudDatumStreamPropertyConfiguration;
Expand Down Expand Up @@ -163,6 +167,125 @@ private static String componentValueRef(Long siteId, Long hardwareId, String fie
return "/%d/%d/%s/%s".formatted(siteId, hardwareId, fieldName, fn);
}

@Test
public void dataValues_site() {
// GIVEN
final String tokenUri = "https://example.com/oauth/token";
final String clientId = randomString();
final String username = randomString();
final String password = randomString();
final Long siteId = randomLong();

// configure integration
final CloudIntegrationConfiguration integration = new CloudIntegrationConfiguration(TEST_USER_ID,
randomLong(), now());
// @formatter:off
integration.setServiceProps(Map.of(
AlsoEnergyCloudIntegrationService.OAUTH_CLIENT_ID_SETTING, clientId,
AlsoEnergyCloudIntegrationService.USERNAME_SETTING, username,
AlsoEnergyCloudIntegrationService.PASSWORD_SETTING, password
));
// @formatter:on
given(integrationDao.get(integration.getId())).willReturn(integration);

// @formatter:off
@SuppressWarnings("deprecation")
final ClientRegistration oauthClientReg = ClientRegistration.withRegistrationId("test")
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.clientId(randomString())
.clientSecret(randomString())
.tokenUri(tokenUri)
.build();
// @formatter:on

final OAuth2AccessToken oauthAccessToken = new OAuth2AccessToken(TokenType.BEARER,
randomString(), now(), now().plusSeconds(60));

final OAuth2AuthorizedClient oauthAuthClient = new OAuth2AuthorizedClient(oauthClientReg, "Test",
oauthAccessToken);

given(oauthClientManager.authorize(any())).willReturn(oauthAuthClient);

// request data
final JsonNode resJson = getObjectFromJSON(
utf8StringResource("alsoenergy-hardware-01.json", getClass()), ObjectNode.class);
final var res = new ResponseEntity<JsonNode>(resJson, HttpStatus.OK);
given(restOps.exchange(any(), eq(HttpMethod.GET), any(), eq(JsonNode.class))).willReturn(res);

// WHEN
Iterable<CloudDataValue> results = service.dataValues(integration.getId(),
Map.of("siteId", siteId));

// THEN
then(restOps).should().exchange(uriCaptor.capture(), eq(HttpMethod.GET), any(),
eq(JsonNode.class));

and.then(uriCaptor.getValue()).as("Request URI").isEqualTo(
BASE_URI.resolve(SITE_HARDWARE_URL_TEMPLATE.replace("{siteId}", siteId.toString())
+ "?includeArchivedFields=true&includeDeviceConfig=true"));

// @formatter:off
and.then(results)
.as("Results provided")
.hasSize(2)
.satisfies(devices -> {
and.then(devices)
.element(0)
.as("Identifiers from response")
.returns(List.of(siteId.toString(), "12345"), from(CloudDataValue::getIdentifiers))
.as("Device name from response")
.returns("Elkor Production Meter", from(CloudDataValue::getName))
.as("No reference for device object")
.returns(null, from(CloudDataValue::getReference))
.as("Metadata from response")
.returns(Map.of("functionCode", "PM"
, "serial", "20647"
, "deviceType", "ProductionPowerMeter"
), from(CloudDataValue::getMetadata))
.extracting(CloudDataValue::getChildren, list(CloudDataValue.class))
.as("Has 2 field children")
.hasSize(2)
.satisfies(fields -> {
and.then(fields)
.element(0)
.as("Identifiers from response")
.returns(List.of(siteId.toString(), "12345", "KW"), from(CloudDataValue::getIdentifiers))
.as("Field name from response")
.returns("KW", from(CloudDataValue::getName))
.as("No reference for field object")
.returns(null, from(CloudDataValue::getReference))
.as("No metadata for field object")
.returns(null, from(CloudDataValue::getMetadata))
.extracting(CloudDataValue::getChildren, list(CloudDataValue.class))
.as("Has field function children")
.hasSize(AlsoEnergyFieldFunction.values().length)
.satisfies(functions -> {
for ( int i =0, len = AlsoEnergyFieldFunction.values().length; i < len; i++ ) {
var fn = AlsoEnergyFieldFunction.values()[i];
and.then(functions).element(i)
.as("Identifiers from response")
.returns(List.of(siteId.toString(), "12345", "KW", fn.name()),
from(CloudDataValue::getIdentifiers))
.as("Function name is field + function")
.returns("KW %s".formatted(fn.name()), from(CloudDataValue::getName))
.as("Feference for function object")
.returns("/%s/12345/KW/%s".formatted(siteId, fn.name()),
from(CloudDataValue::getReference))
.as("No metadata for function")
.returns(null, CloudDataValue::getMetadata)
.as("No children for function")
.returns(null, CloudDataValue::getChildren)
;
}
})
;
})
;
})
;
// @formatter:on
}

@Test
public void requestLatest_singleComponent() {
// GIVEN
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"hardware": [
{
"id": 12345,
"stringId": "C11111_S55555_PM1",
"functionCode": "PM",
"flags": [
"IsEnabled"
],
"fieldsArchived": [
"KW",
"Frequency"
],
"name": "Elkor Production Meter",
"lastUpdate": "2024-11-21T17:20:22.9045092-05:00",
"lastUpload": "2024-11-21T17:17:00-05:00",
"iconUrl": "https://alsoenergy.com/Pub/Images/device/7855.png",
"config": {
"deviceType": "ProductionPowerMeter",
"hardwareStrId": "C11111_S55555_PM1",
"hardwareId": 12345,
"address": 3557664960,
"portNumber": 0,
"baudRate": 0,
"comType": "Unknown",
"serialNumber": "20647",
"name": "Elkor Production Meter",
"outputHardwareId": 0,
"weatherStationId": 0,
"meterConfig": {
"scaleFactor": 0.0,
"isReversed": false,
"grossEnergy": "None",
"maxPowerKw": 58444.96,
"maxVoltage": 480.0,
"maxAmperage": 12200.0,
"acPhase": "Wye"
}
}
},
{
"id": 23456,
"stringId": "C22222_S66666_PV0",
"functionCode": "PV",
"flags": [
"IsEnabled"
],
"fieldsArchived": [
"KwAC",
"KwhAC"
],
"name": "SolarEdge SE100K Inverter",
"lastUpdate": "2024-11-21T17:19:03.951886-05:00",
"lastUpload": "2024-11-21T17:17:00-05:00",
"iconUrl": "https://alsoenergy.com/Pub/Images/device/pvpowered.png",
"config": {
"deviceType": "Inverter",
"hardwareStrId": "C22222_S66666_PV0",
"hardwareId": 23456,
"address": 1,
"portNumber": 1,
"baudRate": 9600,
"comType": "Rs485_2Wire",
"name": "SolarEdge SE100K Inverter",
"outputHardwareId": 315520,
"weatherStationId": 315586,
"inverterConfig": [
{
"ratedAcPower": 100.0,
"stringCount": 9,
"modulesPerString": 42,
"wattsPerModule": 395.0,
"azimuth": 45.0,
"tilt": 10.0,
"trackingMode": "Fixed"
}
]
}
}
]
}

0 comments on commit 02662c0

Please sign in to comment.